From fa58b286f5330dfcdbbf20dac3a53b818c51b5d5 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 30 Oct 2026 17:17:01 -0300 Subject: [PATCH 1/2] docs: align site theme and structure with the LambdaGeo book Same Material theme (LambdaGeo CSS, light/dark toggle), MathJax and Mermaid setup, tabbed navigation (Home, Getting started, Guides, Architecture, Reference, Validation) and a common "DisSModel ecosystem" section on the home page. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01S4SjCckAQ8koVsqjYipUHr --- docs/index.md | 13 + docs/javascripts/mathjax-config.js | 9 + docs/javascripts/mermaid-init.js | 3 + docs/stylesheets/lambdageo.css | 481 +++++++++++++++++++++++++++++ mkdocs.yml | 79 ++++- 5 files changed, 569 insertions(+), 16 deletions(-) create mode 100644 docs/javascripts/mathjax-config.js create mode 100644 docs/javascripts/mermaid-init.js create mode 100644 docs/stylesheets/lambdageo.css diff --git a/docs/index.md b/docs/index.md index 55ceb9a..9d7ca1e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -56,3 +56,16 @@ da = cube.load("forest_pct", grid_id="AC/5km") - [**Catalog**](architecture/catalog.md) — SQLite, schema, hash and time series - [**Grids**](guides/grids.md) — snapping to the national mesh, local grids, spatial relations - [**BDC guide**](guides/bdc.md) — integration with Brazil Data Cube and tile-based processing + +--- + +## The DisSModel ecosystem + +| Package | Role | Documentation | +|:---|:---|:---| +| [**dissmodel**](https://github.com/DisSModel/dissmodel) | Core framework: clock, spatial substrates, executor, I/O, visualization | [dissmodel.github.io/dissmodel](https://dissmodel.github.io/dissmodel/) | +| [**disscube**](https://github.com/DisSModel/disscube) | Declarative pipelines that turn raster and vector sources into model-ready grids | [dissmodel.github.io/disscube](https://dissmodel.github.io/disscube/) | +| [**disslucc**](https://github.com/DisSModel/disslucc) | Land use and cover change: demand, potential and allocation | [dissmodel.github.io/disslucc](https://dissmodel.github.io/disslucc/) | +| [dissmodel-ca](https://github.com/DisSModel/dissmodel-ca), [dissmodel-sysdyn](https://github.com/DisSModel/dissmodel-sysdyn), [dissmodel-abm](https://github.com/DisSModel/dissmodel-abm) | Ready-to-use cellular automata, system dynamics and agent-based models | GitHub | + +Developed by the [LambdaGeo](https://github.com/LambdaGeo) group at the Federal University of Maranhão (UFMA). MIT licence. diff --git a/docs/javascripts/mathjax-config.js b/docs/javascripts/mathjax-config.js new file mode 100644 index 0000000..1e76ce2 --- /dev/null +++ b/docs/javascripts/mathjax-config.js @@ -0,0 +1,9 @@ +// Accept $...$ for inline math (as well as \(...\)) in notebook markdown cells. +window.MathJax = { + tex: { + inlineMath: [["$", "$"], ["\\(", "\\)"]], + displayMath: [["$$", "$$"], ["\\[", "\\]"]], + processEscapes: true + }, + options: { skipHtmlTags: ["script", "noscript", "style", "textarea", "pre", "code"] } +}; diff --git a/docs/javascripts/mermaid-init.js b/docs/javascripts/mermaid-init.js new file mode 100644 index 0000000..33759a9 --- /dev/null +++ b/docs/javascripts/mermaid-init.js @@ -0,0 +1,3 @@ +document.addEventListener("DOMContentLoaded", function() { + mermaid.initialize({ startOnLoad: true }); +}); \ No newline at end of file diff --git a/docs/stylesheets/lambdageo.css b/docs/stylesheets/lambdageo.css new file mode 100644 index 0000000..952e52a --- /dev/null +++ b/docs/stylesheets/lambdageo.css @@ -0,0 +1,481 @@ +/* + * LambdaGeo — identidade visual para livros MkDocs Material + * Paleta oficial, tipografia Inter, componentes modernizados. + * Toda a personalização do tema vive neste arquivo; nenhum arquivo + * interno do tema é sobrescrito. + */ + +/* Fonte Inter — pesos 400/500/600/700 (regular + itálico no peso base) */ +@import url("https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap"); + +/* ---------------------------------------------------------------- * + * 1. Variáveis de marca + * ---------------------------------------------------------------- */ +:root { + --lg-primary: #1565c0; /* Azul */ + --lg-secondary: #2e7d32; /* Verde */ + --lg-accent: #00acc1; /* Ciano */ + --lg-dark: #263238; /* Grafite */ + --lg-light: #fafafa; /* Branco */ + + /* Tons derivados, usados em hover/estados e no modo escuro */ + --lg-primary-light: #5e92f3; + --lg-primary-dark: #003c8f; + --lg-secondary-light: #60ad5e; + --lg-secondary-dark: #005005; + --lg-accent-light: #5ddef4; + --lg-accent-dark: #007c91; + --lg-dark-light: #4f5b62; + --lg-dark-dark: #000a12; + + /* Escala tipográfica (pesos Inter: 400/500/600/700) */ + --lg-font-body: 400; + --lg-font-medium: 500; + --lg-font-semibold: 600; + --lg-font-bold: 700; + + /* Ritmo vertical / largura de leitura */ + --lg-reading-width: 46rem; + --lg-radius: 10px; + --lg-radius-sm: 6px; +} + +/* ---------------------------------------------------------------- * + * 2. Tipografia — Inter em toda a documentação + * ---------------------------------------------------------------- */ +:root, +[data-md-color-scheme] { + --md-text-font: "Inter", "-apple-system", "Helvetica Neue", Arial, sans-serif; + /* Fonte monoespaçada do sistema: evita uma segunda requisição de fonte externa */ + --md-code-font: ui-monospace, "SFMono-Regular", "Menlo", "Consolas", + "Liberation Mono", monospace; +} + +.md-typeset { + font-weight: var(--lg-font-body); + line-height: 1.7; +} + +.md-typeset h1, +.md-typeset h2, +.md-typeset h3, +.md-typeset h4, +.md-typeset h5, +.md-typeset h6 { + font-family: var(--md-text-font); + letter-spacing: -0.01em; + color: var(--lg-dark); +} + +[data-md-color-scheme="slate"] .md-typeset h1, +[data-md-color-scheme="slate"] .md-typeset h2, +[data-md-color-scheme="slate"] .md-typeset h3, +[data-md-color-scheme="slate"] .md-typeset h4, +[data-md-color-scheme="slate"] .md-typeset h5, +[data-md-color-scheme="slate"] .md-typeset h6 { + color: var(--lg-light); +} + +.md-typeset h1 { + font-weight: var(--lg-font-bold); + font-size: 2.1rem; + margin-bottom: 1.4rem; +} + +.md-typeset h2 { + font-weight: var(--lg-font-semibold); + font-size: 1.55rem; + margin-top: 2.4rem; + padding-bottom: 0.4rem; + border-bottom: 1px solid var(--md-default-fg-color--lightest); +} + +.md-typeset h3 { + font-weight: var(--lg-font-semibold); + font-size: 1.2rem; + margin-top: 1.9rem; +} + +.md-typeset h4 { + font-weight: var(--lg-font-medium); + font-size: 1.05rem; +} + +.md-typeset p, +.md-typeset ul, +.md-typeset ol { + margin-bottom: 1.1rem; +} + +/* Navegação lateral, breadcrumbs e TOC usam peso médio para hierarquia clara */ +.md-nav { + font-weight: var(--lg-font-medium); + font-size: 0.7rem; +} + +.md-nav__title { + font-weight: var(--lg-font-semibold); +} + +.md-nav__item .md-nav__link--active, +.md-nav__link:focus, +.md-nav__link:hover { + color: var(--lg-primary); +} + +[data-md-color-scheme="slate"] .md-nav__item .md-nav__link--active { + color: var(--lg-accent-light); +} + +/* ---------------------------------------------------------------- * + * 3. Paleta — modo claro + * ---------------------------------------------------------------- */ +[data-md-color-scheme="default"] { + --md-primary-fg-color: var(--lg-primary); + --md-primary-fg-color--light: var(--lg-primary-light); + --md-primary-fg-color--dark: var(--lg-primary-dark); + --md-primary-bg-color: var(--lg-light); + --md-primary-bg-color--light: var(--lg-light); + + --md-accent-fg-color: var(--lg-accent); + --md-accent-fg-color--transparent: rgba(0, 172, 193, 0.1); + --md-accent-bg-color: var(--lg-light); + + --md-default-bg-color: var(--lg-light); + --md-default-fg-color: var(--lg-dark); + --md-default-fg-color--light: #445056; + --md-default-fg-color--lighter: #78878e; + --md-default-fg-color--lightest: #e3e8ea; + + --md-typeset-color: var(--lg-dark); + --md-typeset-a-color: var(--lg-primary); + + --md-footer-bg-color: var(--lg-dark); + --md-footer-bg-color--dark: var(--lg-dark-dark); + --md-footer-fg-color: var(--lg-light); + --md-footer-fg-color--light: rgba(250, 250, 250, 0.7); +} + +/* ---------------------------------------------------------------- * + * 4. Paleta — modo escuro (alto contraste sobre grafite) + * ---------------------------------------------------------------- */ +[data-md-color-scheme="slate"] { + --md-primary-fg-color: var(--lg-dark); + --md-primary-fg-color--light: var(--lg-dark-light); + --md-primary-fg-color--dark: var(--lg-dark-dark); + --md-primary-bg-color: var(--lg-light); + + --md-accent-fg-color: var(--lg-accent-light); + --md-accent-fg-color--transparent: rgba(93, 222, 244, 0.15); + + --md-default-bg-color: #1b2529; + --md-default-fg-color: #eceff1; + --md-default-fg-color--light: #cfd8dc; + --md-default-fg-color--lighter: #90a4ae; + --md-default-fg-color--lightest: #37474f; + + --md-typeset-color: #eceff1; + --md-typeset-a-color: #7aa9ff; + + --md-code-bg-color: #0f1719; + --md-code-fg-color: #eceff1; + + --md-footer-bg-color: #14181a; + --md-footer-bg-color--dark: #0c0e0f; +} + +/* ---------------------------------------------------------------- * + * 5. Cabeçalho — sem logotipo, apenas título/subtítulo + * ---------------------------------------------------------------- */ +.md-header__title { + font-weight: var(--lg-font-semibold); +} + +.md-header__button.md-logo { + display: none; +} + +/* ---------------------------------------------------------------- * + * 6. Largura de leitura e espaçamento + * ---------------------------------------------------------------- */ +.md-grid { + max-width: 76rem; +} + +.md-typeset { + max-width: var(--lg-reading-width); + margin-left: auto; + margin-right: auto; +} + +/* Tabelas, blocos de código e admonitions podem usar a largura toda do artigo */ +.md-typeset table:not([class]), +.md-typeset pre, +.md-typeset .admonition, +.md-typeset .tabbed-set, +.md-typeset .grid { + max-width: 100%; +} + +@media screen and (max-width: 76.1875em) { + .md-typeset { + max-width: none; + } +} + +/* ---------------------------------------------------------------- * + * 7. Admonitions — cores consistentes com a marca + * ---------------------------------------------------------------- */ +.md-typeset .admonition, +.md-typeset details { + border-radius: var(--lg-radius-sm); + border-width: 1px; +} + +/* Note → azul da marca */ +.md-typeset .admonition.note, +.md-typeset details.note { + border-color: var(--lg-primary); +} +.md-typeset .note > .admonition-title, +.md-typeset .note > summary { + background-color: rgba(21, 101, 192, 0.08); +} +.md-typeset .note > .admonition-title::before, +.md-typeset .note > summary::before { + background-color: var(--lg-primary); +} + +/* Tip / Success → verde da marca */ +.md-typeset .admonition.tip, +.md-typeset details.tip, +.md-typeset .admonition.success, +.md-typeset details.success { + border-color: var(--lg-secondary); +} +.md-typeset .tip > .admonition-title, +.md-typeset .tip > summary, +.md-typeset .success > .admonition-title, +.md-typeset .success > summary { + background-color: rgba(46, 125, 50, 0.08); +} +.md-typeset .tip > .admonition-title::before, +.md-typeset .tip > summary::before, +.md-typeset .success > .admonition-title::before, +.md-typeset .success > summary::before { + background-color: var(--lg-secondary); +} + +/* Question / Info → ciano da marca (complementa nota/dica) */ +.md-typeset .admonition.question, +.md-typeset details.question, +.md-typeset .admonition.info, +.md-typeset details.info { + border-color: var(--lg-accent); +} +.md-typeset .question > .admonition-title, +.md-typeset .question > summary, +.md-typeset .info > .admonition-title, +.md-typeset .info > summary { + background-color: rgba(0, 172, 193, 0.08); +} +.md-typeset .question > .admonition-title::before, +.md-typeset .question > summary::before, +.md-typeset .info > .admonition-title::before, +.md-typeset .info > summary::before { + background-color: var(--lg-accent); +} + +/* Warning e Danger permanecem no padrão do Material (laranja / vermelho) */ + +/* ---------------------------------------------------------------- * + * 8. Blocos de código + * ---------------------------------------------------------------- */ +.md-typeset pre > code, +.md-typeset .highlight { + border-radius: var(--lg-radius); +} + +.md-typeset pre { + border-radius: var(--lg-radius); + padding: 0.2rem 0.2rem; +} + +.md-typeset code { + border-radius: 4px; + font-weight: var(--lg-font-medium); +} + +.md-typeset .highlighttable, +.md-typeset .highlight pre { + border-radius: var(--lg-radius); +} + +.md-clipboard { + border-radius: 4px; +} + +.md-clipboard:hover { + color: var(--lg-accent); +} + +/* ---------------------------------------------------------------- * + * 9. Tabelas + * ---------------------------------------------------------------- */ +.md-typeset table:not([class]) { + border-radius: var(--lg-radius-sm); + overflow: hidden; + border: 1px solid var(--md-default-fg-color--lightest); +} + +.md-typeset table:not([class]) th { + background-color: var(--lg-primary); + color: var(--lg-light); + font-weight: var(--lg-font-semibold); +} + +.md-typeset table:not([class]) tr:nth-child(even) { + background-color: rgba(21, 101, 192, 0.035); +} + +[data-md-color-scheme="slate"] .md-typeset table:not([class]) tr:nth-child(even) { + background-color: rgba(255, 255, 255, 0.03); +} + +/* ---------------------------------------------------------------- * + * 10. Links, botões, abas + * ---------------------------------------------------------------- */ +.md-typeset a { + font-weight: var(--lg-font-medium); + text-decoration-thickness: 1px; + text-underline-offset: 2px; +} + +.md-typeset a:hover { + color: var(--lg-accent); +} + +.md-typeset .md-button { + border-radius: var(--lg-radius-sm); + font-weight: var(--lg-font-semibold); + border-color: var(--lg-primary); + color: var(--lg-primary); +} + +.md-typeset .md-button:hover, +.md-typeset .md-button--primary:hover { + background-color: var(--lg-primary); + border-color: var(--lg-primary); + color: var(--lg-light); +} + +.md-typeset .md-button--primary { + background-color: var(--lg-primary); + border-color: var(--lg-primary); + color: var(--lg-light); +} + +.md-typeset .tabbed-set > .tabbed-labels > label { + font-weight: var(--lg-font-medium); + border-radius: var(--lg-radius-sm) var(--lg-radius-sm) 0 0; +} + +.md-typeset .tabbed-set > input:checked + label { + color: var(--lg-primary); + border-color: var(--lg-primary); +} + +/* ---------------------------------------------------------------- * + * 11. Cards e grids (pymdownx / Material "grid cards") + * ---------------------------------------------------------------- */ +.md-typeset .grid.cards > ul > li, +.md-typeset .grid > .card { + border-radius: var(--lg-radius); + border: 1px solid var(--md-default-fg-color--lightest); + transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s; +} + +.md-typeset .grid.cards > ul > li:hover { + border-color: var(--lg-accent); + box-shadow: 0 6px 16px rgba(0, 0, 0, 0.08); + transform: translateY(-2px); +} + +/* ---------------------------------------------------------------- * + * 12. Badges (utilitário — para uso opcional em HTML embutido) * + * ---------------------------------------------------------------- */ +.lg-badge { + display: inline-block; + padding: 0.1rem 0.55rem; + border-radius: 999px; + font-size: 0.7rem; + font-weight: var(--lg-font-semibold); + line-height: 1.6; + color: var(--lg-light); + background-color: var(--lg-dark-light); +} + +.lg-badge--primary { background-color: var(--lg-primary); } +.lg-badge--secondary { background-color: var(--lg-secondary); } +.lg-badge--accent { background-color: var(--lg-accent); } + +/* ---------------------------------------------------------------- * + * 13. Rodapé + * ---------------------------------------------------------------- */ +.md-footer-meta { + background-color: var(--lg-dark-dark); +} + +.md-footer__link:hover .md-footer__title, +.md-footer__link:hover .md-footer__direction { + color: var(--lg-accent-light); +} + +/* ---------------------------------------------------------------- * + * 14. Busca + * ---------------------------------------------------------------- */ +.md-search-result__meta { + color: var(--md-default-fg-color--light); +} + +mark { + background-color: rgba(0, 172, 193, 0.35); + color: inherit; +} + +/* ---------------------------------------------------------------- * + * 15. Acessibilidade — foco visível + * ---------------------------------------------------------------- */ +a:focus-visible, +button:focus-visible, +.md-nav__link:focus-visible, +.md-tabs__link:focus-visible, +input:focus-visible { + outline: 2px solid var(--lg-accent); + outline-offset: 2px; + border-radius: 2px; +} + +/* ---------------------------------------------------------------- * + * 16. Responsividade fina + * ---------------------------------------------------------------- */ +@media screen and (max-width: 44.9375em) { + .md-typeset h1 { font-size: 1.7rem; } + .md-typeset h2 { font-size: 1.3rem; } + .md-typeset pre { padding: 0.1rem; } + + /* Material's default mobile gutter (0.8rem) is too tight for long-form + reading; give the text more breathing room on phone-sized screens. */ + .md-content__inner { + margin-left: 1.25rem; + margin-right: 1.25rem; + } +} + +/* Wide display equations scroll sideways instead of being clipped on phones. */ +.md-typeset mjx-container[display="true"], +.md-typeset .arithmatex { + overflow-x: auto; + overflow-y: hidden; + max-width: 100%; +} diff --git a/mkdocs.yml b/mkdocs.yml index 0c812df..21d66d1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,43 +1,82 @@ -site_name: DisSCube Documentation -site_description: Spatial data cube engine of the DisSModel ecosystem +site_name: DisSCube +site_description: "Spatial data cube engine of the DisSModel ecosystem: declarative pipelines that turn raster and vector sources into model-ready grids." +site_author: "LambdaGeo / UFMA" site_url: https://dissmodel.github.io/disscube/ repo_url: https://github.com/DisSModel/disscube repo_name: DisSModel/disscube edit_uri: edit/main/docs/ + theme: name: material - palette: - scheme: slate - primary: indigo - accent: indigo + language: en + font: false features: - navigation.tabs + - navigation.instant + - navigation.tracking + - navigation.sections + - navigation.top + - search.highlight + - search.share + - content.code.annotate - content.code.copy + - content.tabs.link + - content.tooltips - content.action.edit + - toc.follow + palette: + - media: "(prefers-color-scheme: light)" + scheme: default + primary: custom + accent: custom + toggle: + icon: material/weather-sunny + name: Switch to dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: custom + accent: custom + toggle: + icon: material/weather-night + name: Switch to light mode + +plugins: + - search markdown_extensions: + - attr_list + - admonition + - pymdownx.details + - pymdownx.arithmatex: + generic: true - pymdownx.highlight: anchor_linenums: true - pymdownx.inlinehilite - pymdownx.snippets + - pymdownx.tabbed: + alternate_style: true - pymdownx.superfences: custom_fences: - name: mermaid class: mermaid format: !!python/name:pymdownx.superfences.fence_code_format - - admonition - - pymdownx.details + - tables + - toc: + permalink: true + +extra_css: + - stylesheets/lambdageo.css + +extra_javascript: + - javascripts/mathjax-config.js + - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js + - https://unpkg.com/mermaid@10/dist/mermaid.min.js + - javascripts/mermaid-init.js nav: - Home: index.md - - Examples: examples.md - - Architecture: - - Overview: architecture/overview.md - - Operator System: architecture/operators.md - - Catalog & Persistence: architecture/catalog.md - - Pipeline Stages: architecture/pipeline.md - - Master Grids & Tiling: architecture/tiling.md - - TerraME Fill Cells Correspondence: terrame_fill_correspondence.md + - Getting started: + - Examples: examples.md - Guides: - Pipeline files (TOML): guides/pipeline_files.md - Command line: guides/cli.md @@ -48,3 +87,11 @@ nav: - "OpenStreetMap": guides/osm.md - "Elevation and slope (DEM)": guides/dem.md - Custom Grids: guides/grids.md + - Architecture: + - Overview: architecture/overview.md + - Operator System: architecture/operators.md + - Catalog & Persistence: architecture/catalog.md + - Pipeline Stages: architecture/pipeline.md + - Master Grids & Tiling: architecture/tiling.md + - Validation: + - "TerraME Fill Cells correspondence": terrame_fill_correspondence.md From 1e260d2d61eae39df6c3ae06cca29bd5be4f4b48 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 31 Oct 2026 07:31:17 -0300 Subject: [PATCH 2/2] docs: TOML-first home page with the main commands The home page now opens with the pipeline file as the main path: a complete TOML, the five commands, the plot of the quickstart result, what a run leaves behind, sources and operators, and the hand-off to a model. The Python API becomes the secondary route. Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01S4SjCckAQ8koVsqjYipUHr --- docs/assets/quickstart.png | Bin 0 -> 34181 bytes docs/index.md | 168 ++++++++++++++++++++++++++++--------- 2 files changed, 129 insertions(+), 39 deletions(-) create mode 100644 docs/assets/quickstart.png diff --git a/docs/assets/quickstart.png b/docs/assets/quickstart.png new file mode 100644 index 0000000000000000000000000000000000000000..3e646affcfc3a1b67918cc7e7bbfffc8b351cb59 GIT binary patch literal 34181 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 B(-r^# literal 0 HcmV?d00001 diff --git a/docs/index.md b/docs/index.md index 9d7ca1e..646dd87 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,61 +1,151 @@ # DisSCube -DisSCube is the spatial data cube engine of the **DisSModel** ecosystem. It converts raw geospatial sources into derived variables aligned to LUCC (Land Use and Cover Change) modeling grids. +**Model-ready grids from heterogeneous raster and vector sources, declared in one TOML file.** -## Core concept +You say which grid you want, which sources feed it and which variables to derive. DisSCube checks the file, fetches +the data, aligns everything to the grid and gives back the variables, with the provenance of every number. It is the +data-preparation step of the **DisSModel** ecosystem, and the counterpart of TerraME's `fill`. +```bash +pip install disscube +disscube validate pipeline.toml # check the file; nothing is downloaded +disscube run pipeline.toml -o cube.tif ``` -SpatialSource → Derivation → Variable → DerivedVariable (Zarr) + +--- + +## One file describes the whole preparation + +```toml +schema = 1 +name = "Quickstart" + +[grid] # where: a 300 m grid +name = "demo/300m" +crs = "EPSG:31983" +bbox = [570000.0, 9708000.0, 582000.0, 9720000.0] +resolution = 300 + +[[source]] # from what: files, MapBiomas, BDC, OSM… +id = "landuse" +type = "file" +path = "../data/quickstart/landuse.tif" +crs = "EPSG:31983" + +[[source]] +id = "elevation" +type = "file" +path = "../data/quickstart/elevation.tif" +crs = "EPSG:31983" + +[[derive]] # which variables: one block each +target = "forest_pct" +source = "landuse" +operator = "percentage" +class_code = 3 + +[[derive]] +target = "landuse_major" +source = "landuse" +operator = "majority" + +[[derive]] +target = "elev_mean" +source = "elevation" +operator = "mean" ``` -A **source** (`SpatialSource`) goes through a **derivation** that applies an **operator** on a **grid** (`GridSpec`), producing a variable registered in the SQLite catalog and stored in Zarr. +The file is the experiment: it can be versioned, reviewed and re-run, and a change in any line changes the +fingerprint (`spec_hash`) of the variables it affects. This is `examples/pipelines/quickstart.toml`, which runs offline on the +synthetic data of the repository. -## Quick install +**Several years?** One block with `years = [2000, 2020]` and an `id = "lulc_{year}"` makes one source per year, and the +derivations that use `{year}` make one variable per year. See [Pipeline files (TOML)](guides/pipeline_files.md). -```bash -pip install disscube # from PyPI -pip install "disscube[dissmodel]" # + hand-off of the cube to DisSModel models -``` +--- + +## The commands -From a clone, for development: +| Command | What it does | +|---|---| +| `disscube validate FILE` | checks the file and prints the plan, before any download | +| `disscube fetch FILE` | downloads the remote sources and checks their SHA-256 | +| `disscube run FILE` | fetches, aligns and derives; with `-o OUT` also exports a GeoTIFF or netCDF | +| `disscube export FILE -o OUT` | exports variables from a workspace that already has them | +| `disscube plot OUT.tif` | a quick look at the bands, as a PNG | ```bash -git clone https://github.com/DisSModel/disscube.git -cd disscube -python -m venv .venv && source .venv/bin/activate -pip install -e ".[dev]" +disscube validate examples/pipelines/quickstart.toml +disscube run examples/pipelines/quickstart.toml --workspace outputs/quickstart -o outputs/quickstart.tif +disscube plot outputs/quickstart.tif --cols 3 ``` -## Main workflow +![The three variables of the quickstart pipeline, drawn by disscube plot: share of forest, dominant class and mean elevation per 300 m cell](assets/quickstart.png) + +Every option is in the [command-line reference](guides/cli.md). Errors exit with status 2 and `--json` gives the +report in a machine-readable form, so a pipeline runs in a script or in CI as it does on a laptop. + +--- + +## What you get + +- **The variables**, one band per variable and year in the exported GeoTIFF (or netCDF), aligned to your grid. +- **A workspace** with the catalog, the stored variables and `run.json`: the sources with their checksums, the + fingerprint of each derivation, the times, and the file that produced it. Rerunning only redoes what changed. +- **Large sources without large memory.** National rasters are read in blocks, so the peak memory depends on the + block and not on the source: MapBiomas for all of Brazil at 5 km ran in under two hours with 3.9 GB + ([block-wise reading](guides/blocks.md)). + +--- + +## Sources in, one value per cell out + +**Sources** (`type`): `file` (a raster, a vector layer or a shapefile in a zip), `mapbiomas` and `prodes` (national +land-cover maps), `bdc` (Brazil Data Cube, over STAC), `classified` (a classified map with its legend), `osm` +(OpenStreetMap), `dem` (elevation and slope) and `union` (several vector sources as one). + +**Operators** summarise a source in each cell: + +- statistics of a raster: `mean`, `median`, `min`, `max`, `std`, `sum` +- classes: `percentage`, `majority`, `minority` (with `groups`, to read leaf classes at level 1) +- vector layers: `distance`, `min_distance`, `count`, `presence`, `area`, `attribute` +- road networks: `network_cost` (also `gpm_network`) + +The sources are in the [pipeline files guide](guides/pipeline_files.md#source-types), and the operators in +[Operator System](architecture/operators.md). From TerraME's `fill`, each operation has a counterpart, measured in +[TerraME Fill Cells Correspondence](terrame_fill_correspondence.md). + +--- + +## Hand the cube to a model + +The workspace opens as a cube in Python, and `disscube[dissmodel]` hands it to a DisSModel model: ```python -from disscube.client import CubeClient -from disscube.models import Derivation - -cube = CubeClient(catalog="catalog.db", store="./data/") - -d = Derivation( - target="forest_pct", - source_id="mapbiomas_2020", - operator="percentage", - class_code=3, - valid_from="2020", - valid_until="2020", -) -cube.derive_declarative(d, grid_id="AC/5km") - -da = cube.load("forest_pct", grid_id="AC/5km") +from disscube import CubeClient + +cube = CubeClient(catalog="outputs/quickstart/catalog.db", store="outputs/quickstart/store/") +ds = cube.to_dataset(["forest_pct", "elev_mean"], grid_id="demo/300m") # xarray.Dataset +backend = cube.to_raster_backend(["forest_pct", "landuse_major"], grid_id="demo/300m") # a DisSModel RasterBackend +``` + +```bash +pip install "disscube[dissmodel]" # + the hand-off to DisSModel +pip install "disscube[plot]" # + disscube plot ``` -## Navigation +The same operations are available as a Python API (`CubeClient`, `Derivation`) for what does not fit in a file: see +[Examples](examples.md). + +--- + +## Where to go next -- [**Examples**](examples.md) — runnable scripts: the API on synthetic data, and TerraME's *Fill* examples reproduced on real data -- [**Architecture**](architecture/overview.md) — conceptual model, pipeline and reproducibility hash -- [**Operators**](architecture/operators.md) — plugin system, available operators, how to add new ones -- [**Pipeline**](architecture/pipeline.md) — detailed stages: Normalizer → GridAligner → Aggregator → Writer -- [**Catalog**](architecture/catalog.md) — SQLite, schema, hash and time series -- [**Grids**](guides/grids.md) — snapping to the national mesh, local grids, spatial relations -- [**BDC guide**](guides/bdc.md) — integration with Brazil Data Cube and tile-based processing +- **[Examples](examples.md)**: the quickstart pipeline and the Python API on synthetic data, then real study areas +- **[Pipeline files (TOML)](guides/pipeline_files.md)**: the format, the source types, the operator options +- **[Command line](guides/cli.md)** and **[Block-wise reading](guides/blocks.md)** +- **Sources**: [MapBiomas](guides/mapbiomas.md), [PRODES and SITS](guides/landcover.md), [Brazil Data Cube](guides/bdc.md), [OpenStreetMap](guides/osm.md), [elevation and slope](guides/dem.md) +- **[Architecture](architecture/overview.md)**: the model, the stages and the reproducibility hash ---