pwaLUPMIS2/dist/assets/xlsx-d1LrY7GM.js
ekke da6f968725 Zonal-statistics Excel export, COG layer entry point, DE Africa ETL
Excel export (from TWG feedback)
- src/analysis/xlsx.js: a dependency-free XLSX writer — an .xlsx is a ZIP of
  XML, so this packs the required parts with a small stored-ZIP writer. Avoids
  SheetJS (stale npm package with advisories) and ExcelJS (heavy for an
  offline-first field app), and does not rely on the JSZip that only reaches us
  transitively via shp-write. Lazy-loaded as a ~5.6 kB chunk.
- After a zonal run the Analysis panel offers "Export table (Excel)", writing a
  two-sheet workbook: Results (figures as real numbers) and Parameters (zone
  and input layers with feature counts, the "Apply to" scope, membership rule,
  statistics, numeric field and export time) — so a table can be verified or
  reproduced later rather than being an unattributed set of numbers.
  The button is hidden for overlay runs and cleared when the mode changes.
- Verified against two independent readers: openpyxl loads it with zero
  warnings and correct numeric types, and LibreOffice Calc opens it as a
  spreadsheet. Also exercised end-to-end through the real zonal pipeline.

COG raster entry point
- Add External Layer gains a COG type alongside WMS/WFS/XYZ, with a URL
  pre-flight check that distinguishes a web page, a 404, a CORS block and a
  server without byte-range support — geotiff.js otherwise reports these only
  as an opaque "AggregateError: Request failed".

Digital Earth Africa ETL
- etl/deafrica_dem_to_minio.py exports a DE Africa DEM for a district as a COG
  and uploads it to the LUSPA MinIO bucket raster-objects, then verifies the
  object is anonymously readable and range-capable. Credentials come from the
  environment; the existing PHP integration hardcodes them and an earlier key
  pair reached Gitea. NOTE: not yet run against the Sandbox — start with
  --list-products to confirm the DEM product name.

Documents
- Concept note: Digital Earth Africa added as a raster source (§6.3), separating
  the live WMS route from the batch Sandbox export; corrected the in-house
  contour table's provenance to OpenTopography (gdal_contour over an SRTM 30 m /
  Copernicus 30 m DEM), and added the ToR §2.4.2 / FS §2 alignment chapter.
- TWG presentation, architecture and two integration workflow charts (SVG
  sources kept in the repo so they stay editable), and a user guide for the
  Analyse tools.

Service worker v13 -> v14. .gitignore: exclude Python bytecode from etl/.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 16:28:45 +02:00

31 lines
5.5 KiB
JavaScript

function u(n){return String(n).replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&apos;").replace(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g,"")}function g(n){let o="",e=n;for(;e>=0;)o=String.fromCharCode(e%26+65)+o,e=Math.floor(e/26)-1;return o}function F(n){return n&&!Array.isArray(n)&&Array.isArray(n.cells)?{cells:n.cells,bold:!!n.bold}:{cells:Array.isArray(n)?n:[n],bold:!1}}function b(n){const o=[];return n.forEach((e,r)=>{const{cells:l,bold:c}=F(e),i=c?' s="1"':"",m=l.map((t,a)=>{const f=`${g(a)}${r+1}`;return t==null||t===""?`<c r="${f}"${i}/>`:typeof t=="number"&&Number.isFinite(t)?`<c r="${f}"${i}><v>${t}</v></c>`:`<c r="${f}"${i} t="inlineStr"><is><t xml:space="preserve">${u(t)}</t></is></c>`}).join("");o.push(`<row r="${r+1}">${m}</row>`)}),`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<worksheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main"><sheetData>${o.join("")}</sheetData></worksheet>`}const k=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<styleSheet xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main">
<fonts count="2"><font><sz val="11"/><name val="Calibri"/></font><font><b/><sz val="11"/><name val="Calibri"/></font></fonts>
<fills count="2"><fill><patternFill patternType="none"/></fill><fill><patternFill patternType="gray125"/></fill></fills>
<borders count="1"><border><left/><right/><top/><bottom/><diagonal/></border></borders>
<cellStyleXfs count="1"><xf numFmtId="0" fontId="0" fillId="0" borderId="0"/></cellStyleXfs>
<cellXfs count="2"><xf numFmtId="0" fontId="0" fillId="0" borderId="0" xfId="0"/><xf numFmtId="0" fontId="1" fillId="0" borderId="0" xfId="0" applyFont="1"/></cellXfs>
<cellStyles count="1"><cellStyle name="Normal" xfId="0" builtinId="0"/></cellStyles>
</styleSheet>`,T=(()=>{const n=new Uint32Array(256);for(let o=0;o<256;o++){let e=o;for(let r=0;r<8;r++)e=e&1?3988292384^e>>>1:e>>>1;n[o]=e>>>0}return n})();function I(n){let o=4294967295;for(let e=0;e<n.length;e++)o=T[(o^n[e])&255]^o>>>8;return(o^4294967295)>>>0}function $(n){const o=new TextEncoder,e=Object.entries(n).map(([s,p])=>({name:s,nameBytes:o.encode(s),data:o.encode(p)})),r=0,l=20513,c=[],i=[];let m=0;const t=s=>[s&255,s>>>8&255],a=s=>[s&255,s>>>8&255,s>>>16&255,s>>>24&255];for(const s of e){const p=I(s.data),d=s.data.length,x=[...a(67324752),...t(20),...t(0),...t(0),...t(r),...t(l),...a(p),...a(d),...a(d),...t(s.nameBytes.length),...t(0)];c.push(new Uint8Array(x),s.nameBytes,s.data),i.push([...a(33639248),...t(20),...t(20),...t(0),...t(0),...t(r),...t(l),...a(p),...a(d),...a(d),...t(s.nameBytes.length),...t(0),...t(0),...t(0),...t(0),...a(0),...a(m)]),i.push(s.nameBytes),m+=x.length+s.nameBytes.length+d}const f=[];let h=0;for(const s of i){const p=s instanceof Uint8Array?s:new Uint8Array(s);f.push(p),h+=p.length}const y=new Uint8Array([...a(101010256),...t(0),...t(0),...t(e.length),...t(e.length),...a(h),...a(m),...t(0)]);return new Blob([...c,...f,y],{type:"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"})}function w(n){const o=(n||[]).filter(l=>l&&Array.isArray(l.rows));if(o.length===0)throw new Error("No sheets to export.");const e={},r=o.map((l,c)=>{const i=`xl/worksheets/sheet${c+1}.xml`;e[i]=b(l.rows);const m=String(l.name||`Sheet${c+1}`).replace(/[:\\/?*[\]]/g," ").slice(0,31);return{id:c+1,name:m,file:i}});return e["[Content_Types].xml"]=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
<Default Extension="rels" ContentType="application/vnd.openxmlformats-package.relationships+xml"/>
<Default Extension="xml" ContentType="application/xml"/>
<Override PartName="/xl/workbook.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml"/>
<Override PartName="/xl/styles.xml" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml"/>
${r.map(l=>`<Override PartName="/${l.file}" ContentType="application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"/>`).join(`
`)}
</Types>`,e["_rels/.rels"]=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
<Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument" Target="xl/workbook.xml"/>
</Relationships>`,e["xl/workbook.xml"]=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<workbook xmlns="http://schemas.openxmlformats.org/spreadsheetml/2006/main" xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships">
<sheets>${r.map(l=>`<sheet name="${u(l.name)}" sheetId="${l.id}" r:id="rId${l.id}"/>`).join("")}</sheets>
</workbook>`,e["xl/_rels/workbook.xml.rels"]=`<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
${r.map(l=>`<Relationship Id="rId${l.id}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/worksheet" Target="worksheets/sheet${l.id}.xml"/>`).join(`
`)}
<Relationship Id="rIdStyles" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/styles" Target="styles.xml"/>
</Relationships>`,e["xl/styles.xml"]=k,$(e)}function S(n,o){const e=URL.createObjectURL(n),r=document.createElement("a");r.href=e,r.download=o,document.body.appendChild(r),r.click(),r.remove(),setTimeout(()=>URL.revokeObjectURL(e),1e3)}function U(n,o){S(w(n),o)}export{w as buildXlsxBlob,S as downloadBlob,U as exportXlsx};
//# sourceMappingURL=xlsx-d1LrY7GM.js.map