====== Working with Subregions Using Region Manager ====== ===== Overview ===== Some workflows shouldn't run against a whole map at once — a national land-use model might need different parameters per country, or a hydrology model might only make sense computed watershed by watershed. Region Manager is what makes that practical: define the regions once, from a categorical map whose class values are the boundaries, and everything downstream — reading metadata, narrowing to one region, splitting a map into pieces, and merging the pieces back — works against that single definition. ===== Defining regions ===== [[Region Manager]] is the container everything else on this page depends on. ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''regions'' | Input | Categorical Map | Yes | Map of subregions, e.g., countries, states, counties, etc. | | ''borderCells'' | Input | Non Negative Integer Value | No, default 0 | Number of lines and columns used to create an additional border around each region. | | ''regionManager'' | Internal Output | Region Manager | — | The region manager, exposed to functors nested inside the container. | A nonzero ''borderCells'' matters for anything that accumulates across neighboring cells — a cost surface, a neighborhood statistic — since without it, a region's edge cells would be computed as if the world stopped there. **Example** — the shell everything else in this page builds on: sourcesMap := LoadMap "c:/data/sources.tif"; frictionsMap := LoadMap "c:/data/frictions.tif"; regionsMap := LoadCategoricalMap "c:/data/regions.tif" .none .default 0; RegionManager regionsMap 5 {{ // everything below is added here, one section at a time }}; ===== Reading region metadata ===== [[Get All Regions Info]] returns boundary details for every region at once, as a table: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''regionManager'' | Input | Region Manager | No, auto-bound | The region manager to read from. | | ''regionsInfo'' | Output | Table | — | One row per region: ''RegionId*'', ''RegionLines'', ''RegionColumns'', ''RegionTopYCoordinate'', ''RegionLeftXCoordinate'', ''RegionBottomYCoordinate'', ''RegionRightXCoordinate''. | [[Get Region Info]] is the single-region equivalent, useful when you need more than a bounding box for one particular region — it also returns a mask isolating that region and its coordinate reference system: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''regionId'' | Input | Integer Value | No, auto-bound | Which region to describe. | | ''regionManager'' | Input | Region Manager | No, auto-bound | The region manager to read from. | | ''regionMask'' | Output | Categorical Map | — | A map isolating just this region. | | ''regionLines'' | Output | Positive Integer Value | — | The region's height, in cells. | | ''regionColumns'' | Output | Positive Integer Value | — | The region's width, in cells. | | ''regionProjection'' | Output | Projection | — | The coordinate reference system of the source map. | | ''regionTopYCoordinate'', ''regionLeftXCoordinate'', ''regionBottomYCoordinate'', ''regionRightXCoordinate'' | Output | Real Value | — | The region's four coordinate boundaries. | Both are for reading region metadata itself — bounding boxes, a region's mask, its coordinate system: RegionManager regionsMap 5 {{ allRegions := GetAllRegionsInfo; }}; ===== Visiting one region, or every region ===== [[Region]] narrows scope to a single region for whatever's nested inside it: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''regionId'' | Input | Integer Value | No, auto-bound (editable) | Which region to select. | | ''regionManager'' | Input | Region Manager | No, auto-bound | The region manager to select from. | | ''regionId'' | Internal Output | Integer Value | — | Re-exposed to functors nested inside. | | ''regionManager'' | Internal Output | Region Manager | — | Re-exposed to functors nested inside. | A region's ID is one of the regions map's own category values, so the cheapest way to visit every region is [[For Each Category]] against that categorization directly — no table needs to be built and then queried for its keys first, the way looping over [[Get All Regions Info]]'s output would require. [[For Each Category]] takes a Categorization, but since [[Categorical Map Type]] converts to [[Categorization Type]] automatically on any connection, the regions map itself can be passed straight in — there's no need for an explicit extraction step like [[Get Map Categories]] first. As with [[For Each]], [[For Each Category]]'s current-step output isn't named ''regionId'', so [[Region]] is still nested inside to re-expose it under the right name — [[Regionalize Map]] and the rest of this page's functors auto-bind to a port by that exact name. ''step'' itself is an internal output, not an ordinary variable, so it can't be passed as a bare argument directly — it has to be aliased to a variable with ''='' first, the same way [[Region Manager]]'s own confirmed example aliases ''regionManager'' to ''manager'' before using it. [[For Each Region]] bundles "define regions" and "loop over them" into one container, but it has its own required ''regions'' input and creates its own region manager internally, rather than sharing one from an enclosing [[Region Manager]]. That works fine when the loop is the entire task. It doesn't work here, because this page's example needs the *same* region manager instance still available after the loop, for the merge — and [[For Each Region]] can't provide that, since its manager stops existing once its own block ends. RegionManager regionsMap 5 {{ _ := ForEachCategory regionsMap {{ regionId = step; Region regionId {{ // regionManager auto-binds from the enclosing RegionManager. }}; }}; }}; ===== Splitting a global map into regions ===== [[Regionalize Map]] and [[Regionalize Categorical Map]] cut a global map down to one region's extent: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''globalMap'' | Input | Map / Categorical Map | Yes | The map to split. | | ''regionId'' | Input | Integer Value | No, auto-bound | Which region to extract. | | ''keepNonRegionCells'' | Input | Boolean | No, default No | If true, cells outside the region mask (including border cells) are kept rather than set to null. | | ''recreateCategories'' | Input | Boolean | No, default No — [[Regionalize Categorical Map]] only | If true, the region's category set is recomputed from the values actually present in it. If false, the full category set from the global map is kept, even categories absent from this particular region. | | ''regionManager'' | Input | Region Manager | No, auto-bound | The region manager defining the split. | | ''regionalMap'' | Output | Map / Categorical Map | — | The map cut down to this region. | This is where the actual per-region computation happens — split the global inputs, run whatever the model needs against the region-sized pieces, in this case [[Calc Cost Map]]. ''regionId'' and ''regionManager'' both auto-bind here from the enclosing [[Region]], so neither needs to be passed explicitly: Region regionId {{ regionalSources := RegionalizeMap sourcesMap; regionalFrictions := RegionalizeMap frictionsMap; { costs = regionalCost, directions = _ } := CalcCostMap regionalSources regionalFrictions .no; }}; ===== Collecting regional results ===== [[Regional Map]] and [[Regional Categorical Map]] tag a per-region fragment under a shared name as it's produced — neither has an output; they're pure collectors, accumulating fragments as a side effect of the loop running: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''globalMapName'' | Input | Name | Yes | The shared name this regional fragment will be collected under. | | ''regionalMap'' | Input | Map ([[Regional Map]]) / Categorical Map ([[Regional Categorical Map]]) | Yes | The per-region result to store. | | ''regionId'' | Input | Integer Value | No, auto-bound | Which region this fragment belongs to. | | ''regionManager'' | Input | Region Manager | No, auto-bound | The region manager this fragment belongs to. | Region regionId {{ regionalSources := RegionalizeMap sourcesMap; regionalFrictions := RegionalizeMap frictionsMap; { costs = regionalCost, directions = _ } := CalcCostMap regionalSources regionalFrictions .no; RegionalMap "cost_map" regionalCost; }}; ===== Merging regions back into one map ===== [[Merge Regional Maps]] and [[Merge Regional Categorical Maps]] reassemble everything stored under a shared name back into one global mosaic: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''globalMapName'' | Input | Name | Yes | The name the fragments were collected under, via [[Regional Map]] or [[Regional Categorical Map]]. | | ''mergeNonRegionCells'' | Input | Boolean | No, default No | If true, cells outside the region mask (including border cells) are merged in too, rather than ignored. Two overlapping cells can only be combined if they share the same value or one of them is null. | | ''regionManager'' | Input | Region Manager | No, auto-bound | The region manager the fragments belong to. | | ''globalMap'' | Output | Map / Categorical Map | — | The reassembled mosaic. | There's a trap here worth naming: neither merge functor has a ''sequenceInput'' of its own, and nothing about ''globalMapName'' — just a literal string — creates a data dependency on the loop that populated it. Left as-is, the engine has no reason to run the merge *after* the loop rather than before or during it. [[For Each Category]] does expose a ''sequenceOutput'' once the whole loop finishes, but with nowhere on the merge functor to plug it into, the fix is a [[Group]] wrapped around the merge, whose own ''sequenceInput'' the loop's ''sequenceOutput'' connects into. A few more things worth knowing before relying on the merge: * **The merged map's format is inherited from the first fragment stored, not negotiated across all of them.** Cell type, null value, and categorization all come from whichever [[Regional Map]] or [[Regional Categorical Map]] call registered first for that name — only the dimensions come from the regions map itself. If regions are processed in an order that isn't guaranteed, don't rely on which fragment "wins." * **Mismatched null values fail the merge, not silently overwrite each other.** When ''mergeNonRegionCells'' brings overlapping border cells from two different regions together, they can only combine if they agree, or one of them is null — two regions disagreeing on what a cell's value should be raises an error rather than picking one. * **A layer's sparse storage survives the merge**, so long as every regional fragment for that layer used it too — the merged global layer comes out sparse rather than being densified. See [[Map Type]]'s Storage section for what sparse storage means. * **Fragments are consumed, not just read.** Once the merge for a given ''globalMapName'' runs, the manager no longer holds those fragments — there's no merging the same name twice, and nothing left to read back under it afterward. ===== Relaying a Region Manager between functors ===== [[Region Manager Value]] exists purely to pass a Region Manager value through — the same relay role [[Struct Type|Struct]]'s carrier functor plays for structs. Its own input auto-binds to the enclosing container's ''regionManager'' by default and cannot be given as a constant. ===== Putting it together ===== sourcesMap := LoadMap "c:/data/sources.tif"; frictionsMap := LoadMap "c:/data/frictions.tif"; regionsMap := LoadCategoricalMap "c:/data/regions.tif" .none .default 0; RegionManager regionsMap 5 {{ sequenceOut := ForEachCategory regionsMap {{ regionId = step; Region regionId {{ regionalSources := RegionalizeMap sourcesMap; regionalFrictions := RegionalizeMap frictionsMap; { costs = regionalCost, directions = _ } := CalcCostMap regionalSources regionalFrictions .no; RegionalMap "cost_map" regionalCost; }}; }}; _ := Group sequenceOut {{ costMap := MergeRegionalMaps "cost_map" .no; SaveMap costMap "c:/data/cost_map_merged.tif"; }}; }}; One region manager, defined once at the top, is what threads the whole thing together: [[For Each Category]] loops directly against the categories already attached to ''regionsMap'', [[Region]] narrows against the manager once per loop iteration, [[Regional Map]] stores fragments into it, and [[Merge Regional Maps]] — deliberately delayed until the loop's ''sequenceOutput'' fires — reads every one of those fragments back out of the same instance. ===== Computing per-class areas within each region ===== [[Calc Areas]] returns a table of every category's area within a map — this section computes one separately per region and accumulates them into a single table, one region's worth of rows at a time. ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''source'' | Input | Categorical Map | Yes | The map to measure. | | ''useAuthalicArea'' | Input | Boolean | No, default Yes | For geographic (Lat/Long) grids, whether to use an authalic sphere for more accurate area at all latitudes, rather than treating every cell as the same size. | | ''areas'' | Output | Table | — | ''Category*'', ''Area_In_Cells'', ''Area_In_Hectares'', ''Area_In_Square_Meters''. | Since each region produces its own ''Category''-keyed table, and the goal is one combined table across every region, the accumulator needs a second, leading key — ''RegionId'' — so each region's rows land in their own, non-overlapping slice. [[Mux Table]] carries that combined table across loop iterations: ^ Port ^ Direction ^ Type ^ Required? ^ Description ^ | ''initial'' | Input | Table | Yes | The starting value, before any iteration has run. | | ''feedback'' | Input | Table | Yes | The value produced by the current iteration, becoming ''table'' for the next one. | | ''table'' | Output | Table | — | The accumulated value entering the current iteration. | This section doesn't use [[Region]] at all — ''regionId'' is used directly, since [[Regionalize Categorical Map]] and [[Set Table By Key]] both auto-bind or accept it positionally without needing it re-exposed under a container of its own: landscapeMap := LoadCategoricalMap "c:/data/landscape.tif" .none .default 0; regionsMap := LoadCategoricalMap "c:/data/regions.tif" .none .default 0; emptyRegionAreas := Table [ "RegionId*#real", "Category*#real", "Area_In_Cells#real", "Area_In_Hectares#real", "Area_In_Square_Meters#real" ]; RegionManager regionsMap 5 {{ _ := ForEachCategory regionsMap {{ regionId = step; accumulatedAreas := MuxTable emptyRegionAreas nextAccumulatedAreas; regionalLandscape := RegionalizeCategoricalMap landscapeMap regionId; areaTable := CalcAreas regionalLandscape .no; // regionId (an Integer Value) converts to a one-element Tuple // automatically here, since it's a connected variable, not a literal. nextAccumulatedAreas := SetTableByKey accumulatedAreas regionId areaTable; }}; // nextAccumulatedAreas, not accumulatedAreas (the mux's own output, // which lags one iteration behind), holds every region's rows once the // loop finishes. The Table carrier passes it out of the loop's scope, // since := always binds a functor call. allRegionAreas := Table nextAccumulatedAreas; SaveTable allRegionAreas "c:/data/area_by_region.csv"; }}; Unlike the map-merging example earlier on this page, no [[Group]]/''sequenceOutput'' workaround is needed to order the save correctly — ''nextAccumulatedAreas'' is an ordinary data value with a real dependency chain running back through every iteration, so the engine already knows to wait. This loop runs one region strictly after another, not in parallel. Each iteration's ''accumulatedAreas'' is fed directly by the previous iteration's ''nextAccumulatedAreas'', so the next region can't start until the current one has finished and been folded into the running table — even though, taken alone, no region's area calculation actually depends on any other region's.