From b1176be1b6bc46331d925f058d6607c8e3c3e68b Mon Sep 17 00:00:00 2001 From: Andres Chamorro Date: Mon, 4 Nov 2024 09:04:23 -0500 Subject: [PATCH] Revert "[WIP] Feature/ntl metadata" --- space2stats_api/src/README.md | 12 +- .../Space2Stats Metadata Content.xlsx | Bin 16983 -> 31482 bytes .../METADATA/create_stac.py | 37 ++- .../space2stats_ingest/METADATA/get_types.py | 2 +- .../METADATA/link_new_item.py | 151 ---------- .../space2stats-collection/collection.json | 60 ++-- .../space2stats_ntl_2013.json | 283 ------------------ .../space2stats_population_2020.json | 19 +- .../space2stats_ingest/METADATA/types.json | 88 +++--- space2stats_api/src/space2stats_ingest/cli.py | 5 +- .../src/space2stats_ingest/main.py | 23 +- space2stats_api/src/tests/conftest.py | 9 +- .../tests/metadata_tests/test_stac_columns.py | 27 +- 13 files changed, 125 insertions(+), 591 deletions(-) delete mode 100644 space2stats_api/src/space2stats_ingest/METADATA/link_new_item.py delete mode 100644 space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_ntl_2013/space2stats_ntl_2013.json diff --git a/space2stats_api/src/README.md b/space2stats_api/src/README.md index a67d7fe..0883793 100644 --- a/space2stats_api/src/README.md +++ b/space2stats_api/src/README.md @@ -1,16 +1,10 @@ ## space2stats -### Generating Preliminary CATALOG, COLLECTION, and ITEM files +### Generating STAC files - Navigate to the METADATA sub-directory and run the following commands in order: 1. get_types.py 2. create_stac.py - Note that the get types function is reading in a parquet file from the following directory: space2stats_api/src/local.parquet -- Here is a workflow diagram of the initial STAC metadata creation: +- Here is a workflow diagram of the STAC metadata creation: -![Create Stac](../../docs/images/create_stac_workflow.png) - -### Adding new ITEM files -- In link_new_item.py set "Paths and metadata setup" in the main function to point towards the corresponding locally saved parquet file -- Navigate to the METADATA sub-directory and run the following commands in order: - 1. get_types.py - 2. line_new_items.py \ No newline at end of file +![Create Stac](../../docs/images/create_stac_workflow.png) \ No newline at end of file diff --git a/space2stats_api/src/space2stats_ingest/METADATA/Space2Stats Metadata Content.xlsx b/space2stats_api/src/space2stats_ingest/METADATA/Space2Stats Metadata Content.xlsx index c6b34e6bd25339ab87d7ed92dbdeff52b027146e..c3df1c796f7ca8bdeb2df88d0d036c818f948087 100644 GIT binary patch literal 31482 zcmeEtb9g6Tvu8B1ZQHgcwr$(CZQIEtnM}-yZQHhOZRYoTclX}=zIUJ9fA`*glJ9xC zyH0nXI#r*lQ>RK!5*P#-02BZm004jh0I^l=u?G+UfCdr(00{sbNK??(#>v>mNmt3; z&e&0#*3H@qzW@Y?JP!cqi~hfl|H2#?OBuCUrH2=O{PqF|)D$dC=A8#lZ8|+z3loQY zl!7{ls^8ItQSz|qDnuqC$L{hCQjC?5IN>n|BVKT#vS~u)F>VTEGc3}BlJd%WYM=`9 z`t;&;udXyMX>kM;E_w+t;`Diyt*%uYq};7>P{c{-T0+8#%MS7szT0tvGcUD4V zzH%ONZ{O)fys;a}Ekmw~;GnLmS_w;XzKOnpW zZ&|22-?J-mVSbBzxDWYY&1glkuYd-ffZAe-a7Zy}NvX@oljsG~hdceqnpPC_%wTFT zn1faaV$5Sz&~7i3J^ZD2+_QQIbM07quwQ+hpP_9iwZ}xWBE%hyy|Q{V?3L0hG}zB? zru+vcXXk0cvd>LeMO!{xR51Rn;sgd9z2EB>1BF0g_wUryIeX-EU`H@UB)@YAx?_-= zKB={FfH7WO=b{mA(j!8%`EpOZE+;F(zzFo4{@Q+*_z4r8D%)!+QO3-zV?x(>!xj&!tte*atL{|oE+zZrUYg0v(s1ANG3 zz$d|2JJ%{D;fxiX@R799J0L^)yB=G5Tv5yWqsVus{6<`ltSZ+W_w;*{+CsNNcuyC^ z6k&*fER)$*l^)6W-@m~S5*dLaHFKP)1sam|3+o%Tu33d0a zLm-j~kM5ZV)$)h&zF72IKOF1@9j+jjy4ewJaZ$b(0+~F$Zw3oH(Sa2IJ&grrSh;dX z8u2YY>_PXAQ2SGx(np|=J!xQpppwVI`{yJA5LjC0;!(REBk0^GShn>W!}v&+Qd={%(B{h`HyvP8qt+RC(*idV$F?Ah;~58)Nh%Pu0CDcy$lK_OVT06b4tIf&F6B1z!r$#>62SZ=;cCe9kG zo{8IxfgZ`)WEljEF9xKV_M`nzm>%TGKm{SxM0U~nKQM;77O2Id57dDbBg>bhtk}X6 zr&1UbY|}q|*N(LNUtUEOYPAeW51>uyuIgomDTBI3=SFq_b4rW^SD~T-XX6e_JgVxp$$s&DeK2R#vKZ^NcHJ06sV-CNTw!kXuoq=p*6?`f zTL{xt2QeCE2^<=xh->2Wp1|s=(Z^kyjs><<5Bu*m-ZYuO&ck8dSE4_(fvWf#Wz zM@Ec)Z8PCd)$Dn{Y$hcb001TcIN%om{B1V>Ee!lm6AJj{HGk3m*FM^kr7e5up+hbM z-$S=vcZlDYz??lW0(F{TU07mnz8i9>oix6^rei0dL^KHwk>F!U!p)qrwZ1pm6w2By zsS&ARl2HQ{T1}@(R6p*Ht^-HL6;T*j!hkOT*LwSS)_Df2ByMxuO*ZWkK DOfo_P%cQBFR%D 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

TH2CSf@hs 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 literal 16983 zcma*P1z23k);5Z}TaW~IcXxMpcXv;4cXtc!PH;kS3-0a^2p-(IO)@i?lXJfRx%+9l zq3T^;T~&MSRme+$g24d2UgC+m8b5#e>kR_%spn{7?L;U47Y6Dl#^~UVS7c>;mt-$JQm`J$h!@u?y0Gyl4`c&7!Ykl@<0Tp!CIYO6hig$pLxVTy+`-0Q zULJa^Z!VbA)nr>z^bK@o_1W2fc1MB%AB$@7u_8cB<^Mc++l2IB=2Zv|J!T-4Fd(h z+^zp{>wg1Y`2YaU<|Z~K|G<8E|2$zGt_&Oq$OQ)o2=(8vpI`=BcN^mmTHi^*j;Ey$X+%$_EygDS6#4eR1jBQA|%uf?u^Y+4-t!s6N1 z?e^`g$AAyEEbLU--EM8ZfyyeN)w8;rZgevRr#xWH5k5PBa4i0b!#P=BTFLLDK)=78 z`le2xTTE27d{u2L%(F0^rWM1=8^=&z?v@-4!As^sqci0B5O-oMV;KD)e+H#kPZqJS z&RD(kbl&byTUhU(w!Tm1?}^2D^L}`|-Jg_G_x)mAdcuKM9IK9{=XGI2-twFAFnU0%wXKYVN z#YeBJSK`e(9g_D9#R>ELI|HY{ChrT#iZ_UiPh4jasahLZRm z0TbGmY02-*HV8jW)2?$C5XXq!`>buWc8Te`lnQgVj5lUM9*m!yxr@H&@|OnhW~Q1`y5(=gf=L^KyM9%( zyXqDd>_p>**F5%g?Im-BoLH7s0VP{MNp#+@05jn`z;2=2xO*B2_B2l2qpf**XW!@H zqcfyRdt$jP;N9I_+zu9BIr)*OAR3G{m)Y=He(M>3K1dW*0@2aq{dEy%oBvo<(43&? zASE#@8 z?=&Y!IN8jyz=WKhK@@+|4{*Al%TY9@E@UES!Y%Dj*!a4fX8#4!X86Q@nCl{dwJ#rf z$h)SR>HCG{n>I36`a9!BJL2XoT9@IeOk^>0$N4iFRF_(=64+%KRfA(%sKf6|Yuptd zNy^r~3%KlAO|l@6-kG0_Q!uo~v_iVyr`cDA--sig4KDIMYXR>zqaQYO0RO#`{F>U2 z=eDs7yH}z_ofRb+C|8S5A^gxVIVbcDJL_|9yG749wcF{5`NS)?*1A+K-$q~3 zmLMNJeAzDtmU&QmZ1nWR{g_VWWG)G0X+@EG)|r3hPF}uEK&w=41%7TDgVO{Cjc!<#{r@=?!_nE+|qP2nh4Pus=zdUjtB?xvsRefD!b9 z9)5%D=;$bGSgMpSJD(Mz#lpgO;uVdVWQnhevV7fLnQ`UN4LL@8fUG z#%*p|e9Y4g7+hO0s(JDA*hc0bvqupt6g2T$PpW!!4 z)zN;5H)d@<5AAL2R@uR`j?<58GWW$-?)-Eqqh(&a*<;0@B0eqRw&_HNHMva{Se=X& zcNiCUsPE0f@a@jD>u=%i-n=&wO?;b5*o>o6t+>Bzy z>^8QPXRG$$9bfpQ?>_a=>*ix#*d=7IebHunGJJfFF_K$3!N)i*TldPWNLdbwbGZ~@N}IqO4(560!j>Kumq+`y>&=r3 z&&(S$od?ei+mFs|z}28PG2!2hZ`4*$whe4^^%~tCPNcTg?tkcYXpfAW>NPf9i)s_b zJ&9`*Y?2HNoAfSxSz@WrTjyS_t6Ps0j<^byXDGvl-%wLxG7-BkUl|BI{*gd)^~L+CqCC4 zxL>R>`v=1b;EahQ;abG%DUzNAcZ9CtjEpCdAe0C}DVU5Tvzm-c&}19o4IDMt0=p6i zu1J{frHW8f;dY5qvU)HzI021={F3?e#+vp!LYUI;wQU*mSB*K_hvc~& z&GEYVAIRZC=wqk$+0$el853jPE``uf@WPA}z!fm3%N8kR!J!j}TnqFIJ&;@f#Brs` zx-+67BOVK3n&SmsAVDZ%SCUOz$OUt5<$6JZ9_P~om`dh9uxCB6Um_#&eMg1xo>OZ^ zG}svM`qg@#7iNvuKX^?5SHyk7>mi(>c$W@;ADVJ3lq%SWXh2`MRfzK!eH@^{$oK(Z zHLe?;nKNFo6J7|f;^4JGq%~f*{@3l3?Z}gD=acOTF_>Srto+oGS9XsV!K=U*Afi5| z8=w_M)NhzE$RBMW^@s*JUT7ymnBo?Hi9pHuX^QmPQwaiyK$%+q6Fqstu7UC!A9|3Hg6LaSVR?^|H9E6exrgCCsCm%m(z0lda(ab5{VYth@!7zlkA)fOWUNb$$vuWcyLTLT{yh zPpY82MR-aIW5NG^(EY?e%YFOegMN%3Sz1# z0VFLWi>|+^7tD)dE0_WLL=}SP7Y2bz!5+H}+5Y|HJV@nxKR^sE`4| zNXy5M=9g%HQzUBvCGq|_bxnFrP4Sxg&J`HSC;@p37%3yTe%AlLP0}^>gDaA%*g>y? zdrjebqeZ16pp!Auw7O1fP@q)HzY}8tl=cwE;p&02lO*8KMld5p&?b*BwhnMT!QoW zvia$S-wb^+05M5NMKPzQ!TuWvyNc#7sicl=1c>RBp?{$Wz}1^Fe>(<;rV=0{-W|2V zQ|Z-#8x?5SQdEF|0;EWMhIZ}50+ZSR3^r`3Wuc`7Ktus57TUvfQHY}@_XEaGeLZ8> zlq7&9i7bJJ#ZTaG>bd@ZqE|RM^%YEtDvYfQmm?@=>fgk!w&W$uuC9Q$zbyPqBkOb} zC`uUvi^#5pIVCZYpZchdzYZ{tjKuya$^62kU;5}c|7(P)_!p`GT+5O5>RMtWCI~|{ zQK@ni>Po^ju;l|de;1dZK079`IRcX^4`u7R=LE!%0m{&dx{^^-GfCe99S|-1;=hNO z%0JXpXK3+&K!jG-)_*m(_)Ct&M}WtF4jl-o3@)p#Pt{P^O=|(9~nQJV~^W7xeBM@b{4UwyMZ|JEWtgr>o?w&suPT^3f3tp5~P!MSlzL$2B@J{{u@(lB(rLNaK@iZ^- zn=z#xbVA2C=^M?@bwmW(D5v9#%Dj%=*yWTFF~kw6f^x)fs*fz@grG#g@hx!V=X#$?V4lZ_UbGdHO3PLL%3~{1cQvm z6}gE-i;Zwc-pv8q_I#az;k78qo2&Go|Gy{XS=^q${r3L zyfa}_V8t*&2zN{;hYYKe7GcWbYkeqgl_`s;ZfJ1yda`pFlXzU z;e}B>BvQ$eL`3g~4bK1V;9Q{Dsuz{1ga*=+#3=sFdnEMy~C2a2( zGJH3Xw8#@fcGUxR(&Tn5WJMlVQ^K@yr5&OfHgM}YF*jBLV!XoWAjWRxQq=?B9ALD`F0bIb~5 z6b(U9&zCD&kD++6iaM8Y z9|Txf^)mwlXjowC3Vj23SORKmTo^CTEiVl*?geCZ^-h08SSfWVeFJb>2u(#VFWajEB@Jgv@8Y^6Wq(oT*{>9Sv+$~5h+<=mz_o*&OC(qz*c z7pfy{8t6`oPEDAecAu{+%Q{%bO%`QauO7bH-mf&$v0c3X^nun*gsEKjtJlp}BbC$e zMh{KC(TtQ<>bVe+wqtFvxa`yLQST2G_DWKg$}uq+DrqX^^ExedF(LPQg`{-RPR|=E`45Se&Qi-Mb*x2+MKhaBTH{; zE<@I#BZ1WA&YkV4!qzeSnj$2!gKbyK>B@cTG&`j1eva?16<4`!?8$KfbJe4IcwozA z`B|a;Nz?Y@$K{j!4bJxVsf;TRRn~G_4>uFjrK=>>*+!1Xk4LOe14YZaeK9@jMP8?D zscEuntcTYv5rv<6txR0Dwzf~d8(%He&z@u}bvvKSvmU3&yc8904b6B9j;gP~| zapa8BP2uEQj?4(cXs<(kbku*$ydmaPM7KYwl6sDVu&Q`(a>d7?!S{M`}6~o4L!ew_<}aeZ5_C zjVkdFR`3*Pk&VDYN>(I9lN%mSFH$P3K&g<(xH^^FzdMnetSDlUn)hWiTk7DGch&bq z7VFfp2C1oCBUf5)laC5Hp$hnzq4I>t2OWq9g@*3_77E(TM5^^SA$w%Z3+*Jr-$?Nx9{V|Y(OhXA9lrhE7O7pvY z$(`{P%#jt$qsbNJf*5LIrvw~mSE&o%Dgc0@zySfc?TY~%Uv4c;h3);*m0kKXCf~GEu9Eq zCb-vt5GE|80MQ7db;8ftK-k%Uy%h)<;(NES^GLQ-+V=a7NoRFTUwBj~98uThZu@$_ zyjWIgVTu$P5Cb3_QY6+9a%;FChI)g%RG6P?;Fu=tFV+Ar(C6OJiy9b_N9MwdVon7s zIDc`HAcs3O2I~&S3X0054`#(Q5XTYig2xVKjr7-I=Eot81u(OLOJZm?NX-3;#1`p4 zw7GxF)QL_SOEp5S2B$)=-&LYtr3R!f3bnY>H`KPGE@Gj%L8 z^=Kw$7n))Gz@h=n4B;3C*h~XZj-IdVJ&u`v-I#&&&MSKH{kA$<`fxb+F3?y;>d_4H zXg4lUAGXFxx@hzx^wOb(;i!b*h6x~GjF5UJ!rWa@03F5oTRv-v{lL&kZlRwu$*NYKhReZI{iPK7p)3U69 zl^Q=yK^oFmu?0O|7&=-|@X%)epr;QV2dBDAJE5F4qWq4fB}i!kH_Et%(#>yf-*|4n zM1|&U`(ejVVQUxf#G%gC9sM~}S( zyNzONR(*~ixXhK;M;6JW(p9H7#I=nPNi1dmv?*~NnYDiN|ZIHe^Ra{g=s`_;4) zP@SIPSFpQ1{HypIzp71>S1@;TKgWRX6=dTks7=qXbx(ked(B^n#!ZvP%`R5%g`WyO z@GqY+E}!Y+;9CDw&@0ErJ?#~Y*!WXH-U-&vBLHwPOR`xX zOzUXy5WQS8+IU%A-B(oULd0}#5LK2Nyu7cqF%IiavR$S(Ys;1{5}vbYUu@l9{&;*H z4|{Cz5pa)V4M?;9ADQ-_&#)`gmTmLJ0hI*c4t&9s=5TSxlf(o|E$SBaOFb-IHaOts zu=bgspI8SrXc?zzWN59GXuS`r^X@tqJ-w(L50lhfNWWIXQ_VNJr-gf|;x=9L`S5u` z+mzeJJ7(|X?Pxt@&0o*2exoc)x$k{%H1st5!74lbo97<8%ZDuy#p>R)l=F;$lJJ7K zjM>T3adf%sRaudfoI?oNIOD=qsudMZ>@E92#b@#K_$nd|QF6SEE#D#cxP z!V5mMWOfwfu}g*RQIDtJ$j&r|U!FYFqN$u`O3mACkbN&%wL!Wgl%i6I5c(AFD3m+n zv?Cm?ykyh55M$YdoI*M-`J7*OAVmQCp<}P5+16&>)fLA~a@lOkfs43&E2Q!X{=^V& zIgxgIJyP#*(X2dc{1X!0asSOz)DrhisZ}ud^+&vTskKLvDxb3>cgvm9&Vu#1@2k&G zar31|O^F%=R!NF+^Q+}5#>W;!4@sY0PF*h6SfxL*%v-N#Ha0?*vj}6>u>ex(Hiapz z$bCLu{wv?p=0}z0aGjA(O&ntTBryCX*e+GA-`ytI=g=I&*R2sBdX-dl18oYi%&1;L zs=PFn)d?g$A*dANX|df66iB#cNNV$8)v36Ev~M`eE~wr#+A{-E9%T=FpiYJk(H!(N@ipn$k1cHWcl zE62o)H${+l@ij%@rh(M;(awCc=f9;der_u9agPFl+Xc)L|9S-ORukg!3+qb}s~!VA zacu^15$4$tl;^CU-8Wwg4t-~^kHu9Wjm#h}==84yrlt@eG@xBmP=bB@HOwF;rQBe~ zV15Vq^iDN%jy1ZNe$~{I4_Dn#o`9Z;S>zHwcNK`L*jtI|PauvO<9g-1$?}IG zWr*FHC}CZ(jX5D&>RXl7Yv+*~7hGI1r8nICVX$B=`h(gN0kqT#DWz<@HGw+80KCpC z{&P-_NOrvw+IMuu~87OLPH`OV*&liae#hmA)aIdel5%i3`Io(0(9(nm@Dxh z(131@AR+Q}6>)Z9LI!L{D8Aj;Cc}}er%ka29wKfx)ng=ojRs0BTnS=PK^(JdsSOT} zbH0Rw)TD-_3n8_}dbKMT*K8bz6J80X@w_(7==J9XSplaV;baYZj^N6(P| zc|X-af{}3#sOfI zgJRp1w@W9I)l)atw9K{h%ab)Id`qKRI;o#>h9vkh(>OO1F3$4#rDb12(-QlRJ}&R-~+(w+i$f;i&`|5JllHJIk0{AfYm9JbOckyq(C-+6KYp|Mh?f_q(_m6ixAEd@fV0AChm@j zBee%nP$sG!pqHC45Pv4G^9vr0C4?0W)6`P!G??5iPJ|7^P;Rph^IJ@OLv2jyMyOzq z4)msTkr~-jiD?Qf@@N;4*86;d`FpItqU69)ic?HU)sIM1;*!|tsmmJVDVDbk+LGEt zk{Izo1M2*d6D}I3I1Go1B!i)*7L*1ShSkBky@}rmfeiMS)~hXCz(5#^_=^xw$Sdz?-@3!AHI0;Bt-xkixyr4dOiati1 zaY-nEJ4nd6&NXU6Vw__VddFi2b|@RNnKJSBTxzhLF$ zjPubWGEl?J_6r}6ZHdZGq`66cVQ^A|vnHkaP`aB`lw$J@FZK^g2hID13HK)6kEVQI zXw`7AS8p-1GCBh87wBsd@o?-rydHU~8{v%Dp4)Y{GR9QXUX7zB=Qa3;cFtD%0lhcNIQsfCWW*>9o~JWpJhbc%Fz z6x%4~puW*X-WvKI!#IAtPVYf=YS!`0v$q=vt*;)7P*s#*>ZS)sDnRD*$FN- zXwCZ<)PVy11jy3r9o-U1%Zum5qeyO#@G6gQPE|+K6vWV->v(&3q-=OXKK7eqbP)Lj zxQL|Nccc2;tsmD{0#WDPs$UqyTCa8GWhDYlMCuqnw3Wj0=gX(tXh(nOnI-oa{VFVv zO8270w($*P{z&gXCyCaq-ArXbaR(`&tn#&PPfN(o*4f0?Sx@=By@`{~>r^qCxNZq3 z@95Vn+;Oc}VC%b{3Zjt*3;_;b;gja)P?euk{by*8*+hw1@EW#J#Xk~8Qt zFcC;ypX#7WReP7~6BWKEY5Ko~hiFoSl-r;v?iZ5okExNj5?d6y`CLmI6h5g@8Ctv3 zp8-1kxLhN?v4mY=-AhG_v^brt)4E*~E{Gdvzp!xBa+;(kT?g?Th=BoH-UZfP)l%4* z%8yk6RN^YqhS5;>B_OL*n6AuJt;OY>sbE$Ns(j1(PDaqa&pKr@$Uc%baQj}6 z-Bxx;;Bi_|&yvb{p2-_ZoC|UxWu3xIx=I=3LNrWFhp%(o-^byoI0*szJXKxB*h6-- zfJs$>HK>@IgE@cHmxD61@@BCv>muQg-Dh=eaBcx02ws5M8VL}De`+-i?CoD8kfo$! zw@eTBNEQ5Xo}9|x0LmEMFNzlQ8X4c>VPvSjQY9HK>_`05{Sd+tr`1he6VZG2cM=LK zS=b5A`vI=3CZ}(tl5SnR{1Q zL25al38`WPYJiNziXpX!lY@C5-c~mQ<5V0CuesGUybGzzK3g6%(9;sN^``v=y$3ay zrl#3~EI>M3W*NKE*D&Th<$1BWZzwQf^wN|8b#OHGTSs_&P9D`LmS3 z1;WeeCBJKd@aVaBl#aZ2z`zg5igSMR`V8Bu;T^aYRxY>@_vtdvk^eDvuP;MoWTQbtJud zF9gKnE|Cj%+aH`1*DUVTTU*66a?rI=PCttF%vcs+BH}JuCw!U)hDC~Eg;}}fLjmjUrY2a*lS6Up`D%8YxIAXFgGOD+6K@g z@%!|(wquH?tEyyDs}xqyJORNXM1ruM>tvkWxM0c0h4%tQ&kS@MpB;H75CwA02OEfz>t!*=CA>^PptP4Y*k|hY%SbL<{SyE9$4Vy`i9BTd)k* z4q-h?9BWGNnNq8pzEe>npJwM3e@OvWDjKY>jzwR|?1&dIj_*kn28Jd)!q)8F$N%msM1uzs`m2HV(|5R;+Rg7x0}X+Y_G@ROt!0P9hT(l$jsK_!S#y${2`156X6InQ6}(#yON{}-A|zoQ z+%ptQq;+@2vlB@>$3YYoC{n9#t*7ZU7!!gwo>XwZneqm`HcdQg=Ep$3YWw?M;kWzp z{RuH*df8Q$b}^~7+z9UrjDGwuryZH3%jU&2!itO>9mp?j7Z5r~lf@>L07o2EhAPh!ZCgBIK}-384C{=s93!^sW+p}*v3nz(xkr@P9mSh1Vf5hkv{p3*1sx=#1 z2am-tQ8Kj%r-5>{LqW@NRx)r*$f)FoHJl`J7-~ORcMa*M^oD(CwZTfE94!u4H!}Ad zq_ZkeJ2p3eJpbd}HRx5|z{Ezf!uEv3Yl4D$9*V52csGQ&&)X!YM~R=ELU-QL-F=rG zl0%GpoRRWwRXU#*BIdh_cXN|m;}JfuO8MNTbbdk*@7BK4z8a6`uQM9>AtZL{qTtds zeu_)@>|@HKb@7f_h`9GR624`MyG4Td?Ds=}n$5>!g7^*_MsapbyzglXynbca!Pjtt zWsXQoTwtYN3CvG{3d$gIyn15F-g;jn@Rm8AEplC!e9bjG?aZfy{MgA>zlVKD;!6uJ&h7neX8aL70USD zgQ`!9?6a2R5;)ae9c%DD9WC$Q!kuQ@3Y2PQnU)I!tw#y4vCdIEL=9lc?Tx@MzO6ZG zx!XMX=Dla_hX1VlY4z%*1t+oN2>QcfHGbKK$5+XT)hgkeB+SCyZbMCEN{K^Pl#wl7v`=o_@I@*XeO~06P5g$k4tU|yG_hd0&8QOQ9OPZ3oL`6* zkH|_R%j&}LC0GFnRtDm}Zy4hKGLaH>8VHg#iydeS_8iIx~gnWk?MI# zNi?6;Xp~KAOC*T|3wjhN2)`i{#n+ag#o?m}P5G6;CFq}55GE*?1POZn(RNukeE2R1=E0yr+!XCp?ETB%NF)8~~A6QoPO;9_s2JAliy%3V#Cygh;P^AnBm?s9E6@gc zY~Hj2!!1NZQ72Yp2l6RSZ0zsI^zn|{V;TJ@bK~xm>hTBsnA->r`T<}e=T?-ux55Jx z9v~OBBZKbU-nS6j=JXiCLa2|2dsk*yeAzYGYGQzdYo!RQYpG~nrA(BqQa+dRIKu2j z;=y~#t0P68HeoY*y?WaRtvM+&~4uqd*7+u(8(=SM!v_cyFk4LdlsvGwmZV~^tgdWN~{}@dcc6dJfkS2y3qFGA?@1)i9u`sJn%sxz2_+kl2-j~d+X)*>BOXTEe z)L;%vqAH^ZM1g88ZXk$f#oAHq;w#D|QEV%zuV&?0LW75!jX@y_e_C57xdc|8^pHA^ z#h!J4KAdMg%8IeY$x;LtB8GWVvuwonHTNL)f*-lR>Q4IU8G9%Sp-y1r!*kB~5%X+K z?b&Hi$e_1wRn2y7a~1XmR6{m|Ig$nO*al2HX!Z`J@#+Z&PN)@D#FvTC9$_RTv>|eV ztP=lV>x1J{#-N&F*+?JDiDXIygkX})No`8}M8yXsnlQ}0J{pR1g?NJ-1x{TLZb6Cb5>~MI2UDbnDtb)angRbG#7FvqK zw@M)tWVtw{jkRH2L*v7R8r*u2(0rT@_Zzld1|>JS2y_Pfkqd|MLb`c>9WlHo7ZMQi zd}rcuHsga0S54OeXLif^%oOP+U_M5e!?yIO9Y~rO9~iGWToHX1@zECDJ*nmk9K|$E z3>j8-&RWXNytqyg_paZKt$6Q9wAH?GRy?8o$87Zx7w_&aW@Be2ijQFzabQyp9N~d- zJd-$ydIDUzl<$M>j&8#;?2c5=;y>X7kcD>TUfX zP0#H6WoV}A@MctfLsF%8Z49)h27|nQ0vjWGM)_ib7;A1(?tg_*! zSZFl##P)mEcN2wLUVUFKcB4yN8q}@domV4j94KOKAtjOA6?@|NdXgnw=0$R#jmicD zW|xqIWHAcqB}7?D)y<(9Q&?BwzhzXx6-+yTVcBOuwJy*Rg`ItS#ELN^*2OJT8yIZc zG?%T~-#l#ZvnAjMd^LjjF66~s1yP=?=K1sdllpjfRo|`ljTH)KRe!0kOeE+|;JyC5 zU?50P*)B3m&4tW}08%FNi(oUToI7Q_3!l)_iX8-Wv1qOZFutxn&#l=9M?DpWLh z{%QpmBKzfb+2cfZ(SfKKL%1{u`H_*g2U0uih*L1Optfd2DNaIIoA(Grj{$pRcoLi}|n`0sn>ClqK(@hA41F^qZbSXlF(BslYqJw*^k zMTQg$e!iPSvTPKiw8%7h6p#;~LT@fK3NP;pX43RqnSnH5@r6xs ze1dX0NEHN)kb%&5+Jw_~JKBkq`cO?j`%;=4A1H=YRx%>6l6&imUZwlvs8CF!?=Mj4xyPtV3PjyM|@rl1EgE3G!hci`7YX!6x3ht3~ZV6F7> zirsvJO#zi9Cu z4Te;D!jB|l55kb4P})Q`*bj$lo--Dop=8y8R3L`cC{0&Ce{z|4Z0wn&sE^DvSv*HvUPY{lCaw4?X|7 zUdc28WIvBU|E=KvPUrk4@c2dW_mSwo$$r`O=b`Ao1OYzs{!RA#i1cqoe=+#;=-qDy zUXcH?@9%?nzsdhw?bowrzpht1hXHkV06YKVP;@=F`DgQMb{>mEvrvC5I@Sk6nTI!#g z|L-s7pE=6kLKpr`_V3yEkMHWQq3~z!?l&JO+Fy?QGn4n*fL|Q`ob!KkK;ix;fPZIA Z<)y%1|5F6up#~~YDj>aD%Jcfy{|5lNT3!GE diff --git a/space2stats_api/src/space2stats_ingest/METADATA/create_stac.py b/space2stats_api/src/space2stats_ingest/METADATA/create_stac.py index c3ab6ad..c1f530e 100644 --- a/space2stats_api/src/space2stats_ingest/METADATA/create_stac.py +++ b/space2stats_api/src/space2stats_ingest/METADATA/create_stac.py @@ -50,7 +50,9 @@ def load_metadata(file: str) -> Dict[str, pd.DataFrame]: # Function to create STAC catalog -def create_stac_catalog(overview: pd.DataFrame, nada: pd.DataFrame) -> Catalog: +def create_stac_catalog( + overview: pd.DataFrame, nada: pd.DataFrame, catalog_dir: str +) -> Catalog: catalog = Catalog( id="space2stats-catalog", description=overview.loc["Description Resource"].values[0], @@ -64,6 +66,8 @@ def create_stac_catalog(overview: pd.DataFrame, nada: pd.DataFrame) -> Catalog: href="https://worldbank.github.io/DECAT_Space2Stats/stac/catalog.json", ) + # catalog.set_self_href(os.path.relpath("catalog.json", start=catalog_dir)) + return catalog @@ -108,11 +112,11 @@ def create_stac_collection(overview: pd.DataFrame) -> Collection: # Function to create STAC Item from GeoDataFrame -def create_stac_item(column_types: dict, metadata: pd.DataFrame) -> Item: +def create_stac_item( + column_types: dict, feature_catalog: pd.DataFrame, item_dir: str +) -> Item: data_dict = [] - feature_catalog = metadata["feature_catalog"] - for column, dtype in column_types.items(): description = feature_catalog.loc[ feature_catalog["variable"] == column, "description" @@ -150,28 +154,26 @@ def create_stac_item(column_types: dict, metadata: pd.DataFrame) -> Item: 89.98750455101016, ] - sources = metadata["sources"] - pop_metadata = sources[sources["Name"] == "Population"].iloc[0] item = Item( id="space2stats_population_2020", geometry=geom, bbox=bbox, datetime=datetime.now(), properties={ - "name": pop_metadata["Name"], - "description": pop_metadata["Description"], - "methodological_notes": pop_metadata["Methodological Notes"], - "source_data": pop_metadata["Source Data"], - "sci:citation": pop_metadata["Citation source"], - "organization": pop_metadata["Organization"], - "method": pop_metadata["Method"], - "resolution": pop_metadata["Resolution"], + "name": "Population Data", + "description": "Gridded population disaggregated by gender for the year 2020, with data available for different age groups.", + "methodological_notes": "Global raster files are processed for each hexagonal grid using zonal statistics.", + "source_data": "WorldPop gridded population, 2020, Unconstrained, UN-Adjusted", + "sci:citation": "Stevens FR, Gaughan AE, Linard C, Tatem AJ (2015) Disaggregating Census Data for Population Mapping Using Random Forests with Remotely-Sensed and Ancillary Data.", + "organization": "WorldPop, https://www.worldpop.org", + "method": "sum", + "resolution": "100 meters", "table:primary_geometry": "geometry", "table:columns": data_dict, "vector:layers": { "space2stats": column_types_with_geometry, }, - "themes": pop_metadata["Theme"], + "themes": ["Demographics", "Population"], }, stac_extensions=[ "https://stac-extensions.github.io/table/v1.2.0/schema.json", @@ -179,6 +181,7 @@ def create_stac_item(column_types: dict, metadata: pd.DataFrame) -> Item: ], ) + # item.set_self_href(os.path.join("items", f"{item.id}.json")) return item @@ -229,6 +232,7 @@ def main(): catalog = create_stac_catalog( metadata["overview"], metadata["nada"], + join(git_root, metadata_dir, "stac"), ) # Create STAC collection @@ -237,7 +241,8 @@ def main(): # Create STAC item item = create_stac_item( column_types, - metadata, + metadata["feature_catalog"], + join(git_root, metadata_dir, "stac"), ) # Add assets to item diff --git a/space2stats_api/src/space2stats_ingest/METADATA/get_types.py b/space2stats_api/src/space2stats_ingest/METADATA/get_types.py index 37b22f7..daf8eed 100644 --- a/space2stats_api/src/space2stats_ingest/METADATA/get_types.py +++ b/space2stats_api/src/space2stats_ingest/METADATA/get_types.py @@ -33,7 +33,7 @@ def save_parquet_types_to_json(parquet_file: str, json_file: str): if __name__ == "__main__": git_root = get_git_root() - parquet_file = join(git_root, "space2stats_api/src/ntl2012.parquet") + parquet_file = join(git_root, "space2stats_api/src/space2stats.parquet") json_file = join( git_root, "space2stats_api/src/space2stats_ingest/METADATA/types.json" ) diff --git a/space2stats_api/src/space2stats_ingest/METADATA/link_new_item.py b/space2stats_api/src/space2stats_ingest/METADATA/link_new_item.py deleted file mode 100644 index b087081..0000000 --- a/space2stats_api/src/space2stats_ingest/METADATA/link_new_item.py +++ /dev/null @@ -1,151 +0,0 @@ -import ast -import json -import os -from datetime import datetime -from os.path import join -from typing import Dict - -import git -import pandas as pd -from pystac import Asset, CatalogType, Collection, Item -from pystac.extensions.table import TableExtension - - -# Function to get the root of the git repository -def get_git_root() -> str: - git_repo = git.Repo(os.getcwd(), search_parent_directories=True) - return git_repo.git.rev_parse("--show-toplevel") - - -# Function to load metadata from the Excel file -def load_metadata(file: str) -> Dict[str, pd.DataFrame]: - overview = pd.read_excel(file, sheet_name="DDH Dataset", index_col="Field") - nada = pd.read_excel(file, sheet_name="NADA", index_col="Field") - feature_catalog = pd.read_excel(file, sheet_name="Feature Catalog") - sources = pd.read_excel(file, sheet_name="Sources") - sources["Variables"] = sources.apply( - lambda x: ast.literal_eval(x["Variables"]), axis=1 - ) - return { - "overview": overview, - "nada": nada, - "feature_catalog": feature_catalog, - "sources": sources, - } - - -# Function to read the existing STAC collection -def load_existing_collection(collection_path: str) -> Collection: - return Collection.from_file(collection_path) - - -# Function to create a new STAC item -def create_new_item(sources: pd.DataFrame, column_types: dict, item_name: str) -> Item: - # Define geometry and bounding box (you may want to customize these) - geom = { - "type": "Polygon", - "coordinates": [ - [ - [-179.99999561620714, -89.98750455101016], - [-179.99999561620714, 89.98750455101016], - [179.99999096313272, 89.98750455101016], - [179.99999096313272, -89.98750455101016], - [-179.99999561620714, -89.98750455101016], - ] - ], - } - bbox = [ - -179.99999561620714, - -89.98750455101016, - 179.99999096313272, - 89.98750455101016, - ] - - # Get metadata for Population item - src_metadata = sources[sources["Name"] == "Nighttime Lights"].iloc[0] - - # Define the item - item = Item( - id=item_name, - geometry=geom, - bbox=bbox, - datetime=datetime.now(), - properties={ - "name": src_metadata["Name"], - "description": src_metadata["Description"], - "methodological_notes": src_metadata["Methodological Notes"], - "source_data": src_metadata["Source Data"], - "sci:citation": src_metadata["Citation source"], - "method": src_metadata["Method"], - "resolution": src_metadata["Resolution"], - "themes": src_metadata["Theme"], - }, - stac_extensions=[ - "https://stac-extensions.github.io/table/v1.2.0/schema.json", - "https://stac-extensions.github.io/scientific/v1.0.0/schema.json", - ], - ) - - # Add table columns as properties - TableExtension.add_to(item) - table_extension = TableExtension.ext(item, add_if_missing=True) - table_extension.columns = [ - {"name": col, "type": dtype} for col, dtype in column_types.items() - ] - - # Add asset - item.add_asset( - "api-docs", - Asset( - href="https://space2stats.ds.io/docs", - title="API Documentation", - media_type="text/html", - roles=["metadata"], - ), - ) - - return item - - -# Function to add the new item to the existing collection -def add_item_to_collection(collection: Collection, item: Item): - collection.add_item(item) - - -# Save the updated collection -def save_collection(collection: Collection, collection_path: str): - collection.normalize_hrefs(collection_path) - collection.save(catalog_type=CatalogType.RELATIVE_PUBLISHED) - - -# Main function -def main(): - git_root = get_git_root() - metadata_dir = join(git_root, "space2stats_api/src/space2stats_ingest/METADATA") - - # Paths and metadata setup - item_name = "space2stats_ntl_2013" - collection_path = join(metadata_dir, "stac/space2stats-collection/collection.json") - excel_path = join(metadata_dir, "Space2Stats Metadata Content.xlsx") - column_types_file = join(metadata_dir, "types.json") - - # Load metadata and column types - metadata = load_metadata(excel_path) - with open(column_types_file, "r") as f: - column_types = json.load(f) - - # Load existing collection - collection = load_existing_collection(collection_path) - - # Create a new item - new_item = create_new_item(metadata["sources"], column_types, item_name) - - # Add the new item to the collection - collection.add_item(new_item, title="Space2Stats NTL 2013 Data Item") - - # Save the updated collection - save_collection(collection, collection_path) - - -if __name__ == "__main__": - main() diff --git a/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/collection.json b/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/collection.json index 1de5433..04c8bdf 100644 --- a/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/collection.json +++ b/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/collection.json @@ -21,12 +21,6 @@ "href": "./space2stats_population_2020/space2stats_population_2020.json", "type": "application/json", "title": "Space2Stats Population Data Item" - }, - { - "rel": "item", - "href": "./space2stats_ntl_2013/space2stats_ntl_2013.json", - "type": "application/json", - "title": "Space2Stats NTL 2013 Data Item" } ], "Title": "Space2Stats Database", @@ -38,28 +32,12 @@ "hexagons", "global" ], - "title": "Space2Stats Collection", - "extent": { - "spatial": { - "bbox": [ - [ - -180.0, - -90.0, - 180.0, - 90.0 - ] - ] - }, - "temporal": { - "interval": [ - [ - "2020-01-01T00:00:00Z", - null - ] - ] + "summaries": { + "datetime": { + "min": "2020-01-01T00:00:00Z", + "max": null } }, - "license": "CC-BY-4.0", "providers": [ { "name": "World Bank", @@ -70,12 +48,6 @@ "url": "https://www.worldbank.org/" } ], - "summaries": { - "datetime": { - "min": "2020-01-01T00:00:00Z", - "max": null - } - }, "assets": { "documentation": { "href": "https://space2stats.ds.io/docs", @@ -85,5 +57,27 @@ "metadata" ] } - } + }, + "title": "Space2Stats Collection", + "extent": { + "spatial": { + "bbox": [ + [ + -180.0, + -90.0, + 180.0, + 90.0 + ] + ] + }, + "temporal": { + "interval": [ + [ + "2020-01-01T00:00:00Z", + null + ] + ] + } + }, + "license": "CC-BY-4.0" } \ No newline at end of file diff --git a/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_ntl_2013/space2stats_ntl_2013.json b/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_ntl_2013/space2stats_ntl_2013.json deleted file mode 100644 index f50c6c5..0000000 --- a/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_ntl_2013/space2stats_ntl_2013.json +++ /dev/null @@ -1,283 +0,0 @@ -{ - "type": "Feature", - "stac_version": "1.0.0", - "stac_extensions": [ - "https://stac-extensions.github.io/table/v1.2.0/schema.json", - "https://stac-extensions.github.io/scientific/v1.0.0/schema.json" - ], - "id": "space2stats_ntl_2013", - "geometry": { - "type": "Polygon", - "coordinates": [ - [ - [ - -179.99999561620714, - -89.98750455101016 - ], - [ - -179.99999561620714, - 89.98750455101016 - ], - [ - 179.99999096313272, - 89.98750455101016 - ], - [ - 179.99999096313272, - -89.98750455101016 - ], - [ - -179.99999561620714, - -89.98750455101016 - ] - ] - ] - }, - "bbox": [ - -179.99999561620714, - -89.98750455101016, - 179.99999096313272, - 89.98750455101016 - ], - "properties": { - "name": "Nighttime Lights", - "description": "Sum of luminosity values measured by monthly composites from VIIRS satellite.", - "methodological_notes": "Monthly composites generated by NASA through the Lights Every Night partnership.", - "source_data": "World Bank - Light Every Night, https://registry.opendata.aws/wb-light-every-night/", - "sci:citation": null, - "method": "sum", - "resolution": "500 mts", - "themes": "Socio-economic", - "table:columns": [ - { - "name": "hex_id", - "description": "H3 unique identifier", - "type": "object" - }, - { - "name": "SUM_VIIRS_NTL_201301", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201301", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201301", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201301", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201302", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201302", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201302", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201302", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201303", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201303", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201303", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201303", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201304", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201304", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201304", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201304", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201305", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201305", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201305", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201305", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201306", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201306", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201306", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201306", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201307", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201307", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201307", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201307", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201308", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201308", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201308", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201308", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201309", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201309", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201309", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201309", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201310", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201310", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201310", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201310", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201311", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201311", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201311", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201311", - "type": "float64" - }, - { - "name": "SUM_VIIRS_NTL_201312", - "type": "float64" - }, - { - "name": "MIN_VIIRS_NTL_201312", - "type": "float64" - }, - { - "name": "MAX_VIIRS_NTL_201312", - "type": "float64" - }, - { - "name": "MEAN_VIIRS_NTL_201312", - "type": "float64" - } - ], - "datetime": "2024-10-30T17:00:16.514238Z" - }, - "links": [ - { - "rel": "root", - "href": "../../catalog.json", - "type": "application/json", - "title": "Space2Stats Database" - }, - { - "rel": "collection", - "href": "../collection.json", - "type": "application/json", - "title": "Space2Stats Collection" - }, - { - "rel": "parent", - "href": "../collection.json", - "type": "application/json", - "title": "Space2Stats Collection" - } - ], - "assets": { - "api-docs": { - "href": "https://space2stats.ds.io/docs", - "type": "text/html", - "title": "API Documentation", - "roles": [ - "metadata" - ] - } - }, - "collection": "space2stats-collection" -} \ No newline at end of file diff --git a/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_population_2020/space2stats_population_2020.json b/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_population_2020/space2stats_population_2020.json index 9febf04..b41a284 100644 --- a/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_population_2020/space2stats_population_2020.json +++ b/space2stats_api/src/space2stats_ingest/METADATA/stac/space2stats-collection/space2stats_population_2020/space2stats_population_2020.json @@ -40,14 +40,14 @@ 89.98750455101016 ], "properties": { - "name": "Population", - "description": "Gridded population disaggregated by gender.", + "name": "Population Data", + "description": "Gridded population disaggregated by gender for the year 2020, with data available for different age groups.", "methodological_notes": "Global raster files are processed for each hexagonal grid using zonal statistics.", - "source_data": "WorldPop gridded population, 2020, Unconstrained, UN-Adjusted, https://www.worldpop.org/methods/top_down_constrained_vs_unconstrained/", - "sci:citation": "Stevens FR, Gaughan AE, Linard C, Tatem AJ (2015) Disaggregating Census Data for Population Mapping Using Random Forests with Remotely-Sensed and Ancillary Data. ", - "organization": "World Pop, https://www.worldpop.org/methods/populations", + "source_data": "WorldPop gridded population, 2020, Unconstrained, UN-Adjusted", + "sci:citation": "Stevens FR, Gaughan AE, Linard C, Tatem AJ (2015) Disaggregating Census Data for Population Mapping Using Random Forests with Remotely-Sensed and Ancillary Data.", + "organization": "WorldPop, https://www.worldpop.org", "method": "sum", - "resolution": "100 mts", + "resolution": "100 meters", "table:primary_geometry": "geometry", "table:columns": [ { @@ -296,8 +296,11 @@ "geometry": "geometry" } }, - "themes": "Demographics", - "datetime": "2024-10-30T13:43:45.940644Z" + "themes": [ + "Demographics", + "Population" + ], + "datetime": "2024-10-24T14:54:26.131129Z" }, "links": [ { diff --git a/space2stats_api/src/space2stats_ingest/METADATA/types.json b/space2stats_api/src/space2stats_ingest/METADATA/types.json index 3463637..29a504b 100644 --- a/space2stats_api/src/space2stats_ingest/METADATA/types.json +++ b/space2stats_api/src/space2stats_ingest/METADATA/types.json @@ -1,50 +1,42 @@ { - "SUM_VIIRS_NTL_201301": "float64", - "MIN_VIIRS_NTL_201301": "float64", - "MAX_VIIRS_NTL_201301": "float64", - "MEAN_VIIRS_NTL_201301": "float64", - "SUM_VIIRS_NTL_201302": "float64", - "MIN_VIIRS_NTL_201302": "float64", - "MAX_VIIRS_NTL_201302": "float64", - "MEAN_VIIRS_NTL_201302": "float64", - "SUM_VIIRS_NTL_201303": "float64", - "MIN_VIIRS_NTL_201303": "float64", - "MAX_VIIRS_NTL_201303": "float64", - "MEAN_VIIRS_NTL_201303": "float64", - "SUM_VIIRS_NTL_201304": "float64", - "MIN_VIIRS_NTL_201304": "float64", - "MAX_VIIRS_NTL_201304": "float64", - "MEAN_VIIRS_NTL_201304": "float64", - "SUM_VIIRS_NTL_201305": "float64", - "MIN_VIIRS_NTL_201305": "float64", - "MAX_VIIRS_NTL_201305": "float64", - "MEAN_VIIRS_NTL_201305": "float64", - "SUM_VIIRS_NTL_201306": "float64", - "MIN_VIIRS_NTL_201306": "float64", - "MAX_VIIRS_NTL_201306": "float64", - "MEAN_VIIRS_NTL_201306": "float64", - "SUM_VIIRS_NTL_201307": "float64", - "MIN_VIIRS_NTL_201307": "float64", - "MAX_VIIRS_NTL_201307": "float64", - "MEAN_VIIRS_NTL_201307": "float64", - "SUM_VIIRS_NTL_201308": "float64", - "MIN_VIIRS_NTL_201308": "float64", - "MAX_VIIRS_NTL_201308": "float64", - "MEAN_VIIRS_NTL_201308": "float64", - "SUM_VIIRS_NTL_201309": "float64", - "MIN_VIIRS_NTL_201309": "float64", - "MAX_VIIRS_NTL_201309": "float64", - "MEAN_VIIRS_NTL_201309": "float64", - "SUM_VIIRS_NTL_201310": "float64", - "MIN_VIIRS_NTL_201310": "float64", - "MAX_VIIRS_NTL_201310": "float64", - "MEAN_VIIRS_NTL_201310": "float64", - "SUM_VIIRS_NTL_201311": "float64", - "MIN_VIIRS_NTL_201311": "float64", - "MAX_VIIRS_NTL_201311": "float64", - "MEAN_VIIRS_NTL_201311": "float64", - "SUM_VIIRS_NTL_201312": "float64", - "MIN_VIIRS_NTL_201312": "float64", - "MAX_VIIRS_NTL_201312": "float64", - "MEAN_VIIRS_NTL_201312": "float64" + "hex_id": "object", + "sum_pop_f_0_2020": "float64", + "sum_pop_f_10_2020": "float64", + "sum_pop_f_15_2020": "float64", + "sum_pop_f_1_2020": "float64", + "sum_pop_f_20_2020": "float64", + "sum_pop_f_25_2020": "float64", + "sum_pop_f_30_2020": "float64", + "sum_pop_f_35_2020": "float64", + "sum_pop_f_40_2020": "float64", + "sum_pop_f_45_2020": "float64", + "sum_pop_f_50_2020": "float64", + "sum_pop_f_55_2020": "float64", + "sum_pop_f_5_2020": "float64", + "sum_pop_f_60_2020": "float64", + "sum_pop_f_65_2020": "float64", + "sum_pop_f_70_2020": "float64", + "sum_pop_f_75_2020": "float64", + "sum_pop_f_80_2020": "float64", + "sum_pop_m_0_2020": "float64", + "sum_pop_m_10_2020": "float64", + "sum_pop_m_15_2020": "float64", + "sum_pop_m_1_2020": "float64", + "sum_pop_m_20_2020": "float64", + "sum_pop_m_25_2020": "float64", + "sum_pop_m_30_2020": "float64", + "sum_pop_m_35_2020": "float64", + "sum_pop_m_40_2020": "float64", + "sum_pop_m_45_2020": "float64", + "sum_pop_m_50_2020": "float64", + "sum_pop_m_55_2020": "float64", + "sum_pop_m_5_2020": "float64", + "sum_pop_m_60_2020": "float64", + "sum_pop_m_65_2020": "float64", + "sum_pop_m_70_2020": "float64", + "sum_pop_m_75_2020": "float64", + "sum_pop_m_80_2020": "float64", + "sum_pop_f_2020": "float64", + "sum_pop_m_2020": "float64", + "sum_pop_2020": "float64" } \ No newline at end of file diff --git a/space2stats_api/src/space2stats_ingest/cli.py b/space2stats_api/src/space2stats_ingest/cli.py index e61427d..c23a838 100644 --- a/space2stats_api/src/space2stats_ingest/cli.py +++ b/space2stats_api/src/space2stats_ingest/cli.py @@ -38,7 +38,6 @@ def download(s3_path: str, local_path: str = typer.Option("local.parquet")): def load( connection_string: str, stac_catalog_path: str, # Add the STAC metadata file path as an argument - item_name: str, parquet_file: str = typer.Option("local.parquet"), chunksize: int = 64_000, ): @@ -46,9 +45,7 @@ def load( Load a Parquet file into a PostgreSQL database after verifying columns with the STAC metadata. """ typer.echo(f"Loading data into PostgreSQL database from {parquet_file}") - load_parquet_to_db( - parquet_file, connection_string, stac_catalog_path, item_name, chunksize - ) + load_parquet_to_db(parquet_file, connection_string, stac_catalog_path, chunksize) typer.echo("Data loaded successfully to PostgreSQL!") diff --git a/space2stats_api/src/space2stats_ingest/main.py b/space2stats_api/src/space2stats_ingest/main.py index 8fbe32b..e352789 100644 --- a/space2stats_api/src/space2stats_ingest/main.py +++ b/space2stats_api/src/space2stats_ingest/main.py @@ -7,7 +7,7 @@ from pystac import Catalog from tqdm import tqdm -TABLE_NAME = "NTL2013" +TABLE_NAME = "space2stats" def read_parquet_file(file_path: str): @@ -34,30 +34,23 @@ def read_parquet_file(file_path: str): return table -def get_all_stac_fields(stac_catalog_path: str, item: str) -> Set[str]: +def get_all_stac_fields(stac_catalog_path: str) -> Set[str]: catalog = Catalog.from_file(stac_catalog_path) items = catalog.get_items(recursive=True) columns = [] - - # Filter items to match the given item param for it in items: - if item in it.get_self_href(): - columns.extend( - [col["name"] for col in it.properties.get("table:columns", [])] - ) - break - + columns.extend([col["name"] for col in it.properties.get("table:columns")]) + print(columns) return set(columns) -def verify_columns(parquet_file: str, stac_catalog_path: str, item: str) -> bool: +def verify_columns(parquet_file: str, stac_catalog_path: str) -> bool: """ Verifies that the Parquet file columns match the STAC item metadata columns. Args: parquet_file (str): Path to the Parquet file. stac_metadata_file (str): Path to the STAC item metadata JSON file. - item (str): Name of the relevant STAC item. Returns: bool: True if the columns match, False otherwise. @@ -65,7 +58,8 @@ def verify_columns(parquet_file: str, stac_catalog_path: str, item: str) -> bool parquet_table = read_parquet_file(parquet_file) parquet_columns = set(parquet_table.column_names) - stac_fields = get_all_stac_fields(stac_catalog_path, item) + stac_fields = get_all_stac_fields(stac_catalog_path) + if parquet_columns != stac_fields: extra_in_parquet = parquet_columns - stac_fields extra_in_stac = stac_fields - parquet_columns @@ -94,11 +88,10 @@ def load_parquet_to_db( parquet_file: str, connection_string: str, stac_catalog_path: str, - item: str, chunksize: int = 64_000, ): # Verify column consistency between Parquet file and STAC metadata - if not verify_columns(parquet_file, stac_catalog_path, item): + if not verify_columns(parquet_file, stac_catalog_path): raise ValueError("Column mismatch between Parquet file and STAC metadata") table = pq.read_table(parquet_file) diff --git a/space2stats_api/src/tests/conftest.py b/space2stats_api/src/tests/conftest.py index 694cad0..81d9277 100644 --- a/space2stats_api/src/tests/conftest.py +++ b/space2stats_api/src/tests/conftest.py @@ -130,11 +130,10 @@ def stac_file_path(): @pytest.fixture -def metadata_excel_file_path(): +def types_json_file_path(): current_dir = os.path.dirname(os.path.abspath(__file__)) root_dir = os.path.abspath(os.path.join(current_dir, "../../..")) - metadata_excel_file_path = os.path.join( - root_dir, - "space2stats_api/src/space2stats_ingest/METADATA/Space2Stats Metadata Content.xlsx", + types_json_file_path = os.path.join( + root_dir, "space2stats_api/src/space2stats_ingest/METADATA/types.json" ) - return metadata_excel_file_path + return types_json_file_path diff --git a/space2stats_api/src/tests/metadata_tests/test_stac_columns.py b/space2stats_api/src/tests/metadata_tests/test_stac_columns.py index 18940e9..303790f 100644 --- a/space2stats_api/src/tests/metadata_tests/test_stac_columns.py +++ b/space2stats_api/src/tests/metadata_tests/test_stac_columns.py @@ -1,19 +1,10 @@ import json -import pandas as pd - -def test_stac_columns_vs_types_json(stac_file_path, metadata_excel_file_path): - # Load the expected column types from the Metadata Content Excel - feature_catalog = pd.read_excel( - metadata_excel_file_path, sheet_name="Feature Catalog" - ) - expected_columns = feature_catalog[feature_catalog["source"] == "Population"] - - # Convert the DataFrame to a dictionary for easier comparison - expected_columns_dict = dict( - zip(expected_columns["variable"], expected_columns["type"]) - ) +def test_stac_columns_vs_types_json(stac_file_path, types_json_file_path): + # Load the expected column types from the types JSON file + with open(types_json_file_path, "r") as f: + expected_columns = json.load(f) # Load the STAC item from the JSON file with open(stac_file_path, "r") as f: @@ -24,16 +15,16 @@ def test_stac_columns_vs_types_json(stac_file_path, metadata_excel_file_path): col["name"]: col["type"] for col in stac_item["properties"]["table:columns"] } - # Assert that the number of columns in the STAC file matches the number of columns in the types TABLE file + # Assert that the number of columns in the STAC file matches the number of columns in the types JSON file assert ( - len(stac_columns) == len(expected_columns_dict) - ), f"Mismatch in column count: STAC ({len(stac_columns)}) vs TABLE ({len(expected_columns_dict)})" + len(stac_columns) == len(expected_columns) + ), f"Mismatch in column count: STAC ({len(stac_columns)}) vs JSON ({len(expected_columns)})" # Assert that column names and types match - for column_name, column_type in expected_columns_dict.items(): + for column_name, column_type in expected_columns.items(): assert ( column_name in stac_columns ), f"Column {column_name} is missing in the STAC file" assert ( stac_columns[column_name] == column_type - ), f"Mismatch in column type for {column_name}: STAC ({stac_columns[column_name]}) vs TABLE ({column_type})" + ), f"Mismatch in column type for {column_name}: STAC ({stac_columns[column_name]}) vs JSON ({column_type})"