ParcelFlux

Comprehensive Academic Reference Manual · v0.3.1

Yusuf Eminoğlu · August 2026 · github.com/YusufEminoglu/parcelflux

1. Architecture & Workflow

Plugin Architecture & Design Philosophy

ParcelFlux is a dock-style QGIS plugin that subdivides zoning blocks into parcels with natural variation. Unlike Processing-toolbox plugins that run once and produce a fixed output, ParcelFlux is interactive: you adjust parameters, preview the result as a dashed temporary layer, and either Apply (commit to the project) or Discard (adjust and retry).

The architecture has three layers:

  1. Dock Panel (dialogs/dock.py) — Tabbed QGIS dock widget (Run / Guide / About) with all parameter controls, a Run button, a progress bar, a quality-report card, and Apply/Discard buttons.
  2. Subdivision Engine (algorithms/parcel_flux_core.py) — A standalone ParcelFluxCore class that takes a block layer and 13 parameters, runs the seven-step subdivision algorithm on a background thread, and returns a result layer plus a quality-report dictionary. Zero ProcessingProvider dependency — the engine is callable from headless scripts, tests, or other plugins.
  3. Seed Gallery (dialogs/seed_gallery.py) — Runs four seeds simultaneously on background threads, renders each result as a thumbnail, and shows per-seed quality summaries for side-by-side comparison.

The subdivision engine delegates geometry operations — oriented bounding boxes, line extension, polygon splitting — to QGIS native algorithms via processing.run(), ensuring correctness across QGIS versions without reimplementing computational geometry.

Theoretical Background: Parcel Subdivision

The subdivision of land into parcels is a fundamental step in urban development, mediating between the large-scale structure of road networks and the fine-grained pattern of individual buildings. In urban morphology, the parcel (or lot) is the basic unit of land ownership and development control. The geometric properties of parcels — width, depth, area, and frontage — determine the economic feasibility of development, the typology of buildings that can be accommodated, and the character of the resulting streetscape (Moudon, 1997).

Historically, parcel subdivision has been a manual process performed by surveyors and urban designers, guided by zoning regulations, market demand, and topographical constraints. The automation of this process emerged from two converging research traditions. In computer graphics, procedural urban modelling systems (Parish & Müller, 2001; Müller et al., 2006) introduced rule-based subdivision of city blocks into parcels as a precursor to building generation. The CityEngine platform commercialised these techniques, using recursive OBB-based subdivision and straight-skeleton methods to partition blocks (Vanegas et al., 2012). In geographic information science, Wickramasuriya et al. (2011) developed an automated land subdivision tool for urban and regional planning that evaluates candidate layouts against planning constraints, selecting the configuration that maximises parcel yield while satisfying minimum frontage and area requirements.

ParcelFlux occupies a distinctive position at the intersection of these traditions. It inherits the OBB-based subdivision approach from computer graphics (using oriented bounding boxes as layout guides) but applies it within a GIS-native framework where all geometric operations are performed by QGIS's computational geometry engine (GEOS), and the output is a fully attributed vector layer ready for planning analysis. The plugin's design philosophy — interactive parameter tuning with immediate visual feedback — draws on the concept of guided procedural modelling articulated by Lipp et al. (2008), where the user specifies high-level constraints and the system generates geometrically valid instances within those bounds.

The subdivision problem can be formalised as a constrained space partitioning task: given a polygon \(B\) (the block) bounded by \(m\) line segments representing streets, partition \(B\) into \(n\) sub-polygons \(\{P_1, \ldots, P_n\}\) such that each \(P_i\) satisfies area constraints \(A_{\min} \leq A(P_i) \leq A_{\max}\), frontage constraints \(F(P_i) \geq F_{\min}\), and the union of all \(P_i\) exactly covers \(B\) (the "complete by construction" guarantee). This is a variant of the polygon decomposition problem, which is NP-hard in its general form (Keil, 2000), but becomes tractable when restricted to axis-aligned or OBB-aligned subdivisions of near-rectangular input polygons — the very restriction that the regular vs. irregular block classification (§2, Step 1) exploits.

Dock Panel & Run Workflow

The dock panel organises the workflow into a single scrollable Run tab with grouped controls:

GroupControls
InputBlock layer selector (QgsMapLayerComboBox, filtered to polygon layers), "Selected features only" checkbox, output layer name field.
Core SubdivisionTarget lot width, minimum/maximum area, merge threshold.
Natural VariationWidth variation (±%), fishbone offset (m), row width asymmetry (%), H-line offset (%).
Planning ConstraintsMinimum street frontage (m), parcel depth (m), corner widening (%), uniform corners.
Output OptionsAdd ID fields, add facade metrics fields, seed value.

The Run button dispatches the subdivision to a QThread worker. A progress bar tracks completion. On finish, the result appears as a dashed-border temporary layer with a Result card showing the quality report. Apply solidifies the layer and adds it to the project; Discard removes it. The Compare Seeds button opens the seed gallery.

Reset defaults. A Reset button restores all 18 parameters to their factory values. This is useful after extensive experimentation — one click returns to a known-good starting point.

Interpretation Guide: Architecture

Headless integration. The ParcelFluxCore engine is callable from Python scripts without any QGIS GUI dependency. This enables batch processing of hundreds of blocks, integration with web-based planning platforms, and automated scenario generation for urban simulation models. The engine returns a tuple of (result_layer, error_message) — zero exit-like semantics that simplify error handling in automated pipelines.

Background execution. The QThread worker design means the QGIS interface remains responsive during subdivision. For large datasets (200+ blocks), progress updates at block granularity allow the user to monitor completion. The cancellation system (§6) provides cooperative termination within one block's processing time (typically < 1 second), so the user never waits long for a cancel to take effect.

Processing delegation rationale. By delegating all geometric operations to QGIS native algorithms rather than reimplementing them, ParcelFlux avoids the version-compatibility fragility that plagues many QGIS plugins. The same processing.run("native:orientedminimumboundingbox") call works across QGIS 3.28 through 4.x because QGIS, not the plugin, is responsible for maintaining the algorithm's correctness.

2. The Subdivision Algorithm

Seven-Step Algorithm Overview

The engine processes each input block polygon through seven sequential stages. Every stage operates on the actual block geometry — the oriented bounding box (OBB) is used only as a layout guide for division-line orientation and spacing, never as a clipping boundary.

  1. Classify — Is the block regular (4-vertex, near-rectangular) or irregular (5+ vertices, L-shaped, etc.)?
  2. Orient — Find the two shortest edges → block ends. Division direction is perpendicular to these.
  3. Row detect — Is the block narrow enough for one parcel row, or wide enough for two opposing rows?
  4. Generate division lines — Perpendicular lines at lot_width spacing, with optional variation, fishbone offset, asymmetry, and H-line shift.
  5. Split & clip — Extend lines beyond the block, clip to block boundary, split the polygon.
  6. Merge residuals — Three-pass merging: slivers (area < 5 m²), undersized parcels, and under-fronted parcels merge into the neighbour with the longest shared edge.
  7. Filter & flag — Parcels exceeding max_area are bisected (up to 5 recursive levels). Anything still outside the envelope is kept and flagged in pf_flag. The quality report is computed.

Mathematical Formulation: Algorithm Overview

Formally, ParcelFlux solves the following constrained optimisation problem for each input block polygon \(B\):

$$\begin{aligned} \text{Partition } B &\rightarrow \{P_1, P_2, \ldots, P_k\} \\ \text{subject to:} \quad & \bigcup_{i=1}^{k} P_i = B \quad \text{(complete coverage)} \\ & A(P_i) \in [A_{\min}, A_{\max}] \quad \forall i \quad \text{(area bounds)} \\ & F(P_i) \geq F_{\min} \quad \forall i \quad \text{(minimum frontage)} \\ & k \rightarrow \max \quad \text{(maximise parcel count)} \end{aligned} \tag{1}$$

The objective of maximising parcel count (subject to minimum area constraints) corresponds to the planning goal of efficient land utilisation: each parcel represents a developable unit, and more parcels generally mean more housing or commercial units within the same block area. This is consistent with the planning rationale articulated by Wickramasuriya et al. (2011), who select the subdivision layout that generates the highest number of lots.

Step 1: Block Classification

Blocks with exactly 4 vertices (after geometry repair via makeValid()) are classified as regular. Their OBB is used for orientation and division-line generation, and the actual block boundary is used for clipping. Blocks with 5+ vertices are classified as irregular — their OBB is still used as a layout guide, but all clipping and splitting respects the real (possibly L-shaped, non-convex) footprint.

Mathematical Formulation: Block Classification

A block polygon \(B\) is classified as regular if and only if its outer ring contains exactly 5 points — 4 distinct vertices plus the closing vertex:

$$\text{is\_regular}(B) = \big( \text{valid}(B) \land |\text{outer\_ring}(B)| = 5 \big) \tag{2}$$

where \(\text{valid}(B)\) is determined by GEOS's isGeosValid() predicate, which checks for self-intersections, ring orientation errors, and other topological validity conditions. Multi-part polygons (MultiPolygon) are decomposed via asMultiPolygon() and each part classified independently. Invalid geometries are repaired by native:fixgeometries, which applies GEOS's makeValid() to produce a topologically valid output.

For irregular blocks, the oriented bounding box (OBB) is computed via native:orientedminimumboundingbox, which uses the rotating calipers algorithm to find the minimum-area enclosing rectangle. This OBB becomes the split_layer — a fast-to-split 4-vertex rectangle — while the repaired original polygon is retained as the real_layer for final clipping.

Step 2: Orientation & Division Direction

The algorithm identifies the two shortest edges of the OBB as the block ends (the street-facing sides). The division direction is perpendicular to the short-edge direction — i.e., division lines run parallel to the long edges, cutting the block into parcels that each have frontage on the short edge (street).

Mathematical Formulation: OBB & Division Direction

Given a block polygon's outer ring vertices \(\{\mathbf{v}_0, \mathbf{v}_1, \ldots, \mathbf{v}_{n-1}\}\), the edge lengths are:

$$L_i = \|\mathbf{v}_{(i+1) \bmod n} - \mathbf{v}_i\| \quad \text{for } i = 0, \ldots, n-1 \tag{3}$$

The two edges with the smallest \(L_i\) are identified. Their midpoints \(\mathbf{m}_0, \mathbf{m}_1\) are computed at a position ratio \(h_{\text{ratio}}\) along each edge:

$$\mathbf{m}_k = \mathbf{v}_{\text{start}}^{(k)} + h_{\text{ratio}} \cdot \big( \mathbf{v}_{\text{end}}^{(k)} - \mathbf{v}_{\text{start}}^{(k)} \big), \quad k \in \{0, 1\} \tag{4}$$

where \(h_{\text{ratio}}\) is determined by the H-line offset parameter or parcel depth constraint (see §3). The division line connects \(\mathbf{m}_0\) and \(\mathbf{m}_1\). Its direction angle is:

$$\alpha = \text{atan2}(m_{1,y} - m_{0,y},\; m_{1,x} - m_{0,x}) \tag{5}$$

and the perpendicular division direction is:

$$\alpha_{\perp} = \alpha + \frac{\pi}{2} \tag{6}$$

The cross-width — the block depth available for front and rear parcel rows — is the length of the longer of the two short edges:

$$w_{\text{cross}} = \max(L_{\text{short},0},\; L_{\text{short},1}) \tag{7}$$

Step 3: Single vs Double-Row Detection

$$\text{row\_mode} = \begin{cases} \text{single} & \text{if } w_{\text{short}} < 1.8 \times w_{\text{lot}} \\ \text{double} & \text{otherwise} \end{cases} \tag{8}$$

where \(w_{\text{short}}\) is the length of the block's short edge and \(w_{\text{lot}}\) is the target lot width. In double-row mode, a centreline is drawn along the block's long axis, and each side of the block is subdivided independently. In single-row mode, division lines span the full block width. The 1.8× threshold ensures that double-row parcels have roughly the target lot width on each side — narrower blocks would produce parcels too shallow to be useful.

Mathematical Formulation: Row Detection

The row detection threshold is derived from the geometric requirement that a double-row parcel must accommodate both a front setback and a rear courtyard while maintaining usable building depth. With a typical front setback of 5 m, a rear setback of 3 m, and a minimum building depth of 8 m, the minimum parcel depth is approximately 16 m — roughly equal to the default lot_width. The 1.8 factor provides a safety margin:

$$w_{\text{cross}} \geq 1.8 \cdot w_{\text{lot}} \implies \frac{w_{\text{cross}}}{2} \geq 0.9 \cdot w_{\text{lot}} \tag{9}$$

Each side of the double row gets approximately \(w_{\text{cross}} / 2\) of depth, which at the threshold equals \(0.9 \cdot w_{\text{lot}}\) — just below the target width, ensuring parcels remain roughly square (width-to-depth ratio near 1.0).

In single-row mode, no H-line (centreline) is created, and division lines cut fully across the block in the perpendicular direction. In double-row split mode (when row width asymmetry > 0), two independent sequences of half-lines are generated, one in each perpendicular direction from the centreline.

Step 4: Division Line Generation

Perpendicular division lines are generated at intervals of lot_width along the block's long axis. Four optional modifiers introduce natural variation:

ModifierParameterEffect
Width variationwidth_variation (±%)Each division interval = lot_width × (1 + random(−v, +v)). Produces non-uniform parcel widths for a natural streetscape.
Fishbone offsetfishbone_offset (m)Zigzag displacement at division-line endpoints perpendicular to the division direction. Mimics the organic boundary offsets of traditional settlement morphology.
Row width asymmetryrow_width_asymmetry (%)Different lot widths on opposite sides of the centreline: side A = lot_width × (1 + a), side B = lot_width × (1 − a). Produces differentiated front/rear facades.
H-line offsethline_offset (%)Shifts the centreline away from the geometric midpoint by this fraction of the half-width. Creates asymmetric front/rear depths when one side should have deeper parcels.

Mathematical Formulation: Division Lines & Variation

Parcel count per row. Given a division line length \(\ell\) and target lot width \(w_{\text{lot}}\), the base number of parcels per row is:

$$n_{\text{seg}} = \left\lfloor \frac{\ell}{w_{\text{lot}}} \right\rfloor, \quad n_{\text{seg}} \geq 1 \tag{10}$$

Width variation. Each parcel width is individually scaled:

$$w_i = w_{\text{lot}} \cdot \big( 1 + U(-\sigma_w, +\sigma_w) \big), \quad i = 0, \ldots, n_{\text{seg}}-1 \tag{11}$$

where \(\sigma_w = \text{width\_variation} / 100\) and \(U(a,b)\) is a uniform random variate from the LCG (§6). The widths are then renormalised to preserve the total row length:

$$w_i' = w_i \cdot \frac{n_{\text{seg}} \cdot w_{\text{lot}}}{\sum_{j=0}^{n_{\text{seg}}-1} w_j} \tag{12}$$

This renormalisation ensures that the row spans exactly the division line length, preventing gaps or overruns at the block boundary.

Corner widening. When corner widening is active and \(n_{\text{seg}} \geq 3\):

$$w_0' = w_0 \cdot (1 + \sigma_c), \quad w_{n-1}' = w_{n-1} \cdot (1 + \sigma_c) \tag{13}$$

$$w_i' = w_i \cdot \frac{\sum_{j=1}^{n-2} w_j - \sigma_c(w_0 + w_{n-1})}{\sum_{j=1}^{n-2} w_j}, \quad i = 1, \ldots, n-2 \tag{14}$$

where \(\sigma_c = \text{corner\_widening} / 100\). Corner parcels absorb extra width; interior parcels shrink proportionally.

Division line placement. Cumulative offset along the division line:

$$s_k = s_0 + \sum_{i=0}^{k-1} w_i', \quad k = 1, \ldots, n_{\text{seg}}-1 \tag{15}$$

where \(s_0 = (\ell - \sum_i w_i') / 2\) for uniform corner mode, or \(s_0 = 0\) otherwise. Each division point is obtained by interpolating along the division line at distance \(s_k\).

Fishbone offset. At each division point, independent random offsets are applied to the left and right endpoints along the division line direction:

$$\begin{aligned} \Delta_L &= U(-d_f, +d_f) \cdot \mathbf{d}_{\text{along}} \\ \Delta_R &= U(-d_f, +d_f) \cdot \mathbf{d}_{\text{along}} \end{aligned} \tag{16}$$

where \(d_f = w_{\text{lot}} \cdot (\text{fishbone\_offset} / 100)\) and \(\mathbf{d}_{\text{along}}\) is the unit vector along the division line. The perpendicular endpoints are then displaced by these offsets, producing slanted division lines.

Row width asymmetry. For double-row blocks with asymmetry:

$$w_A = w_{\text{lot}} \cdot \big( 1 + U(-\sigma_a, +\sigma_a) / 100 \big) \tag{17}$$

$$w_B = w_{\text{lot}} \cdot \big( 1 - U(-\sigma_a, +\sigma_a) / 100 \big) \tag{18}$$

where \(\sigma_a = \text{row\_width\_asymmetry}\). Side A gets wider parcels; side B correspondingly narrower.

H-line offset. The centreline position ratio is:

$$h_{\text{ratio}} = 0.5 + \frac{U(-h, +h)}{100} \tag{19}$$

where \(h = \text{hline\_offset}\). When \(h = 0\), the centreline is at the geometric midpoint (\(h_{\text{ratio}} = 0.5\)). When \(h > 0\), the centreline shifts toward one side, creating deeper parcels on that side.

Step 5: Split & Clip to Block Boundary

Division lines are extended slightly beyond the block bounding box, then clipped to the actual block polygon using QGIS's native splitWithLines algorithm. The resulting fragments are the raw parcels. Oversized parcels (area > max_area) are recursively bisected along their long axis, up to _MAX_RESPLIT_DEPTH = 5 levels (turning one parcel into at most 32).

Mathematical Formulation: Split, Clip & Bisection

Line extension and clipping. Division lines are extended by a small distance \(\varepsilon = 0.05\) units at each end using native:extendlines to ensure they fully cross the block boundary. Extended lines are merged via native:mergevectorlayers and clipped to the block polygon using native:clip. The clipped lines are then used as splitting features in native:splitwithlines.

Recursive bisection. A parcel \(P\) with area \(A(P) > A_{\max}\) is bisected recursively. The bisection algorithm:

  1. Computes the OBB of \(P\)
  2. Identifies the longest OBB edge with direction vector \(\mathbf{u} = (u_x, u_y)\)
  3. The perpendicular bisection direction is \(\mathbf{p} = (-u_y, u_x)\)
  4. Two half-plane rectangles are constructed centred on the parcel centroid: $$R_{\pm} = \left\{ \mathbf{c} \pm s \cdot \mathbf{p} + t \cdot \mathbf{u} \;\middle|\; s \in [0, r],\; t \in [-r, r] \right\} \tag{20}$$ where \(r = 2 \cdot (w_{\text{bbox}} + h_{\text{bbox}} + \ell_{\text{long}}) + 10\) — large enough to cover the entire parcel
  5. The parcel is intersected with each half-plane: \(P_{\pm} = P \cap R_{\pm}\)
  6. If either half produces a sliver (area < 5 m²) or non-polygon geometry, the split aborts and \(P\) is kept intact

The recursion depth is bounded by _MAX_RESPLIT_DEPTH = 5, so a single oversized parcel can produce at most \(2^5 = 32\) sub-parcels. This is far beyond any practical planning scenario — even a 10,000 m² parcel bisected 5 times yields parcels of approximately 312 m² each.

Irregular block clipping. For irregular blocks, the OBB-split parcels are clipped to the repaired real polygon using native:clip. Parcels entirely outside the real polygon are discarded; parcels partially outside are trimmed to the real boundary.

Step 6: Three-Pass Residual Merging

Residual fragments are eliminated in three sequential passes, each targeting a different failure mode:

PassTargetCriterionMerge Into
1. Sliver mergeDust fragmentsArea < 5.0 m²Neighbour with longest shared edge
2. Undersize mergeBelow-minimum parcelsArea < min_area OR area < merge_thresholdNeighbour with longest shared edge
3. Frontage mergeUnder-fronted parcelsStreet frontage < min_frontageNeighbour with longest shared edge

Each pass iterates until no more fragments meet the criterion. The "longest shared edge" rule ensures fragments merge into the geometrically most natural neighbour — the one they share the most boundary with — rather than an arbitrary adjacent polygon.

Guaranteed coverage. After all three passes, any fragment that still exists (area > max_area after 5 re-split levels, or area below thresholds with no merge-eligible neighbour) is kept and flagged rather than deleted. The output always covers 100% of the input block area minus only what is explicitly flagged. This is the "complete by construction" guarantee: no parcel is ever silently discarded.

Mathematical Formulation: Residual Merging

Dynamic threshold. The merge threshold is computed as a fraction of the mean parcel area within the current set of features:

$$T_{\text{merge}} = \sigma_m \cdot \frac{\sum_{f \in \mathcal{F}} A(f)}{|\mathcal{F}|} \tag{21}$$

where \(\sigma_m = \text{merge\_threshold} / 100\). This adaptive threshold ensures consistent behaviour across blocks of different sizes — the same percentage setting produces comparable merging behaviour whether a block yields 4 parcels or 40.

Shared-edge computation. For a candidate parcel \(P_c\) and a neighbour \(P_n\) identified via spatial index query:

$$L_{\text{shared}}(P_c, P_n) = \text{length}\!\big( \text{boundary}(P_c) \cap \text{boundary}(P_n) \big) \tag{22}$$

The intersection is computed as a GEOS geometry intersection, and its length is taken if the result is a LineString. The neighbour with the maximum \(L_{\text{shared}}\) is selected for merging:

$$P_n^* = \underset{n \in \mathcal{N}(c)}{\arg\max}\; L_{\text{shared}}(P_c, P_n) \tag{23}$$

Merge operation. The merge is performed via GEOS's combine() (equivalent to set-theoretic union):

$$P_{\text{merged}} = P_c \cup P_n^* \tag{24}$$

Up to three passes are performed. After each pass, the mean area and threshold are recomputed. The loop terminates early if a pass performs zero merges. The final feature set is captured after the last pass.

Frontage computation. The street frontage of a parcel is computed by testing each edge segment of the parcel's boundary against the parent block's boundary edges. An edge segment \((a, b)\) touches the parent block boundary if the intersection has non-zero length:

$$F(P) = \sum_{e \in \text{edges}(P)} \mathbb{1}\!\big[ \exists \; e' \in \text{edges}(B) : \text{length}(e \cap e') > \varepsilon \big] \cdot \text{length}(e) \tag{25}$$

where \(\varepsilon = 0.001\) m is a numerical tolerance. Parent block edges are pre-indexed in a QgsSpatialIndex for efficient nearest-neighbour lookup.

Step 7: Filter, Flag & Output

The final step assigns each parcel a pf_flag value: empty string (OK), oversize (area > max_area after max re-splits), undersize (area < merge_threshold but no merge-eligible neighbour), or narrow (frontage < min_frontage but no merge-eligible neighbour). The quality-report dictionary is computed from the final parcel set and returned alongside the output layer.

Interpretation Guide: The Algorithm

When fishbone produces plausible layouts. The fishbone offset was inspired by the irregular settlement patterns documented in traditional Anatolian and Mediterranean urban fabrics. Values of 0.5–2.0 m produce subtle boundary irregularities characteristic of organically grown settlements. Values above 5 m produce exaggerated zigzag patterns that may be appropriate for conceptual design exploration but rarely correspond to real cadastral patterns. The fishbone effect is most visually convincing when combined with width variation (10–20%), as the two modifiers interact to break the mechanical regularity of purely orthogonal subdivision.

Row mode selection logic. The 1.8× threshold between single and double row is a design parameter, not a mathematical necessity. Blocks with cross-width between 1.0× and 1.8× the lot width will be assigned single-row mode, producing wide but shallow parcels. In planning practice, such parcels are appropriate for attached housing typologies (townhouses, row houses) where the parcel extends from street to rear lane. Blocks with cross-width below 1.0× the lot width will produce parcels narrower than deep — a configuration more common in historic urban cores.

Merge pass ordering. The three passes execute in fixed order (slivers → undersized → frontage) because each pass may create conditions relevant to the next. Merging a sliver into its neighbour changes the neighbour's area, which could push it below the undersize threshold. Merging an undersized parcel changes its frontage. The iterative nature of each pass (repeating until no more merges occur) handles chains of merges where merging A into B makes B's area too small, triggering B's merge into C.

3. Parameters

Core Subdivision Parameters

ParameterTypeDefaultRangeDescription
lot_widthDouble16.08–50 mTarget parcel width along the street frontage. The primary control on parcel count.
min_areaDouble300.050–2000 m²Minimum acceptable parcel area. Parcels below this trigger the undersize merge pass.
max_areaDouble2000.0200–10000 m²Maximum acceptable parcel area. Parcels above this are recursively bisected. Must be > min_area.
merge_thresholdDouble35.010–500 m²Area below which a parcel is a candidate for merging in pass 2. Typically set to 10–20% of the target parcel area.

Width Variation, Fishbone & Asymmetry

ParameterTypeDefaultRangeDescription
width_variationDouble0.00–50%Random ± variation in each parcel's width. 0 = uniform widths. 20% = widths vary between 0.8× and 1.2× lot_width. Uses the seed for reproducibility.
fishbone_offsetDouble0.00–10 mRandom perpendicular displacement at each division line endpoint. 0 = straight lines. Higher values produce the fishbone/zigzag pattern characteristic of organic settlement fabrics.
row_width_asymmetryDouble0.00–50%Percentage difference in lot width between the two sides of the centreline. 0 = symmetric. 25% = side A gets 1.25×, side B gets 0.75× of lot_width.
hline_offsetDouble0.00–50%Shift of the centreline away from the geometric midpoint, as a fraction of half-width. 0 = centre. 25% = centreline moved 25% toward one side, creating deeper parcels on that side.

Planning Constraints: Frontage, Depth & Corners

ParameterTypeDefaultRangeDescription
min_frontageDouble0.00–50 mMinimum street frontage per parcel. 0 = no constraint. Parcels below this trigger the frontage merge pass (pass 3).
parcel_depthDouble0.00–200 mExplicit front-row parcel depth. 0 = depth determined by the block geometry. When set, front-row parcels are clipped to this depth from the street edge before the split step, creating a consistent building-line depth.
corner_wideningDouble0.00–100%Extra width multiplier for corner parcels (the first and last parcel on each block face). 50% = corner parcels are 1.5× wider than interior parcels.
uniform_cornersBooleanTrueWhen true, all four corner parcels of a double-row block get the same widened width. When false, only the two street-facing corners are widened.

Output & Behaviour Parameters

ParameterTypeDefaultDescription
seedInteger42Random seed for the deterministic LCG. Same seed + same inputs = identical widths, fishbone offsets, and asymmetry placements.
add_id_fieldsBooleanTrueAdd pf_id (sequential parcel ID) and pf_block_id (source block ID) to output parcels.
add_facade_fieldsBooleanTrueAdd computed facade metrics: exterior frontage, side frontage, rear-edge proxy, corner flag, and cardinal front direction.
selected_onlyBooleanFalseSubdivide only the currently selected features in the block layer.
output_nameString"ParcelFlux Parcels"Name for the output temporary layer.

Interpretation Guide: Parameter Selection

Lot width as design driver. The lot width is the single most influential parameter. In planning practice, lot widths are typically specified by zoning ordinances: 10–12 m for row houses, 14–18 m for detached single-family, 20–30 m for multi-family or commercial parcels. Start with the zoning-specified minimum lot width and increase if the resulting parcel count is lower than desired.

Area constraint balance. The min_area and max_area parameters work as a pair. Setting max_area only slightly above min_area (e.g. 400/350) enforces tight uniformity — the bisection and merging passes will aggressively normalise parcel sizes. Setting a wide gap (e.g. 5000/250) permits diverse parcel sizes, which is appropriate for mixed-use areas where commercial anchor parcels coexist with residential infill. The engine will flag (not delete) parcels that cannot be brought within bounds, so the quality report always accurately reflects constraint satisfaction.

Merge threshold calibration. The merge threshold should be set to 10–20% of the product lot_width × (cross_width / 2) — i.e., roughly the area of a "half-width" parcel. A threshold too low (< 5%) allows tiny residual fragments to persist; too high (> 50%) may merge genuinely viable small parcels. The dynamic threshold computation (Equation 21) means the percentage is interpreted relative to the actual mean area, not an absolute value.

Corner widening in planning context. Corner parcels are typically more valuable (dual street frontage, higher visibility) and often accommodate larger buildings or mixed-use programmes. Setting corner_widening to 30–50% reflects common planning practice. The uniform_corners option distinguishes between "all corners are equal" (appropriate for grid-iron blocks where all four corners face intersections) and "only street-facing corners matter" (appropriate for blocks bounded by a rear lane or alley on one side).

4. Input & Output

Input: Block Layer Requirements

PropertyRequirement
Geometry typePolygon or MultiPolygon
CRSProjected (metric). All area, width, and depth parameters are in the layer's map units — for a geographic CRS these would be decimal degrees, producing meaningless results.
Geometry validityInvalid geometries are repaired via makeValid() before processing. The engine tolerates messy cadastral data.
AttributesNo required fields. The block's fid is used as pf_block_id when ID fields are enabled. Existing attribute fields are preserved in the output parcels.

Output: Parcel Attribute Fields

Output parcels inherit all attribute columns from the input block layer. Additional fields are added depending on the output options:

FieldTypeConditionDescription
pf_idIntegeradd_id_fields = TrueSequential 1-based parcel identifier, unique within the run.
pf_block_idIntegeradd_id_fields = TrueSource block feature ID. Links each parcel back to its parent block.
pf_flagStringAlwaysEmpty = OK; oversize = exceeds max_area after max re-splits; undersize = below merge_threshold, unmergeable; narrow = below min_frontage, unmergeable.
pf_areaDoubleAlwaysParcel area in map units² (typically m²).
pf_frontageDoubleAlwaysComputed street frontage length in map units (m).

Computed Facade Metrics

When add_facade_fields = True, the engine computes five additional geometric properties per parcel:

FieldTypeDescription
pf_facade_extDoubleExterior frontage — the length of the parcel edge facing the street (the block's short edge).
pf_facade_sideDoubleSide frontage — the length of the parcel edge perpendicular to the street (the shared side boundary with the adjacent parcel).
pf_facade_rearDoubleRear-edge proxy — the length of the parcel edge opposite the street, or the centreline edge for double-row blocks.
pf_cornerBooleanTrue if the parcel occupies a corner position (first or last on the block face).
pf_cardinalStringCardinal direction of the front facade: N, NE, E, SE, S, SW, W, NW. Computed from the exterior-frontage edge orientation.

Mathematical Formulation: Facade Metrics

Exterior frontage. For each edge segment \(e\) of the parcel boundary, the intersection with each parent block boundary edge \(e' \in \mathcal{E}_B\) is computed:

$$L_{\text{ext}}(e) = \sum_{e' \in \mathcal{E}_B} \text{length}\!\big( e \cap e' \big), \quad \text{clamped to length}(e) \tag{26}$$

Parent block edges are pre-indexed in a spatial index. A bounding-box expansion of 0.001 units provides numerical tolerance. The total exterior frontage is the sum of all edge sharing lengths.

Corner detection. A parcel is classified as a corner if it touches the parent block boundary at more than one edge segment:

$$\text{is\_corner}(P) = \big( |\{e \in \text{edges}(P) : L_{\text{ext}}(e) > 0.01\}| > 1 \big) \tag{27}$$

Cardinal direction. The front direction is computed from the vector from the parcel centroid to the midpoint of the longest exterior edge segment:

$$\mathbf{d}_{\text{front}} = \mathbf{m}_{\text{longest\_ext}} - \mathbf{c}_P \tag{28}$$

$$\theta = \text{atan2}(d_y, d_x) \tag{29}$$

$$\text{direction} = \text{directions}\!\left[ \left\lfloor \frac{\theta \cdot 180/\pi + 22.5}{45} \right\rfloor \bmod 8 \right] \tag{30}$$

where \(\text{directions} = (\text{E}, \text{NE}, \text{N}, \text{NW}, \text{W}, \text{SW}, \text{S}, \text{SE})\).

Width-to-depth ratio. As a compactness proxy:

$$\text{wd\_ratio} = \frac{F_{\text{ext}}^2}{A} \tag{31}$$

A ratio near 1.0 indicates a roughly square parcel (width approximately equals depth). Ratios above 2.0 indicate wide, shallow parcels; ratios below 0.5 indicate narrow, deep parcels.

Quality Report Statistics

After every run, the engine returns a quality-report dictionary with these keys:

KeyTypeDescription
blocks_inIntegerNumber of input blocks processed.
blocks_skippedIntegerBlocks skipped (e.g. non-polygon geometry, zero area after repair).
parcelsIntegerTotal parcels produced.
input_areaDoubleSum of input block areas (m²).
output_areaDoubleSum of output parcel areas (m²).
coverage_pctDouble100 × output_area / input_area. Should always read 100% — anything less indicates flagged parcels were excluded.
area_min / area_mean / area_median / area_maxDoubleArea distribution statistics (m²).
frontage_meanDoubleMean street frontage across all parcels (m).
mergedIntegerNumber of fragments eliminated by the three-pass merge.
resplitIntegerNumber of times an oversized parcel was bisected.
flag_oversize / flag_undersize / flag_narrowIntegerCounts of parcels flagged in each category.

Interpretation Guide: Input & Output

CRS verification. Before running, verify that your block layer's CRS is projected (metric). In QGIS, right-click the layer → Properties → Information → CRS. If it reads "EPSG:4326" or "WGS 84," reproject using Vector → Data Management Tools → Reproject Layer to a suitable UTM zone or national grid. A geographic CRS will produce parcels measured in decimal degrees — a 300 m² minimum area constraint would accept parcels of 300 square-degrees, which is meaningless.

Quality report interpretation. The ideal quality report shows coverage_pct = 100% and zero flags. A non-zero flag_oversize count indicates blocks where the recursive bisection failed — typically very large blocks with irregular shapes where the OBB-based bisection direction produces slivers. Increase max_area or manually split such blocks before running ParcelFlux. A non-zero flag_undersize count indicates parcels that could not be merged because they lack a shared-edge neighbour. These typically occur at the tips of irregular blocks where a small fragment is isolated from the main body — manually inspect and edit these cases.

Facade metrics for building generation. The facade metrics are designed to feed downstream building-generation tools (such as PlanX 3D City Viewer or CityEngine). The pf_facade_ext field directly provides the building frontage width; pf_cardinal enables solar-orientation analysis; pf_corner flags parcels that may accommodate corner-building typologies (dual frontage, chamfered corners).

5. Preview & Seed Gallery

Preview, Apply & Discard Workflow

After a run completes, the output layer is added to the QGIS project as a temporary memory layer with a dashed-border style — visually distinct from committed layers. A Result card in the dock panel shows the quality-report summary (coverage %, parcel count, area range, merge/resplit counts). The user can:

This preview loop encourages iterative design: adjust lot_width → run → inspect frontage distribution → adjust min_frontage → re-run → apply when satisfied.

Because random variation (width, fishbone) makes one parameter set produce many plausible layouts, the Compare Seeds button runs the same parameters with four different seeds (42, 123, 789, 2048) simultaneously on background threads. Each result is rendered as a 260×200 px thumbnail using QGIS's map renderer and displayed in a 2×2 grid dialog with its quality summary (parcel count, coverage %, area range, merges).

The operator picks the preferred layout by clicking its thumbnail, then Apply Selected — that result becomes the temporary preview layer, ready for Apply/Discard. This turns a stochastic process into a visual choice: "I like seed 789's corner-parcel proportions better."

Interpretation Guide: Preview & Seeds

Iterative parameter refinement. The preview workflow is designed around a specific cycle: (1) Set parameters based on zoning requirements, (2) Run, (3) Inspect the quality report — if coverage_pct is below 100%, flagged parcels exist, or the parcel count seems low, adjust parameters accordingly, (4) Use Seed Gallery if the layout is "almost right but the specific random pattern is unlucky," (5) Apply when satisfied. This cycle typically converges in 2–4 iterations per block configuration.

Seed selection strategy. The four gallery seeds (42, 143, 769, 2090) are derived from the base seed by adding offsets (0, 101, 727, 4242). These offsets are chosen to be co-prime-like to avoid correlated random sequences. If none of the four gallery layouts is satisfactory, adjust parameters and re-open the gallery — the underlying issue is likely a parameter mismatch, not bad luck.

Thumbnail interpretation. The 260×200 px thumbnails are sufficient to assess overall layout quality (regularity, corner-parcel proportions, fishbone character) but not to read individual parcel dimensions. For detailed inspection, Apply the selected seed and inspect the full-resolution layer in the QGIS map canvas with labels enabled for pf_area and pf_frontage.

6. Advanced Topics

The Random Number Generator (LCG)

ParcelFlux uses a self-contained 64-bit linear congruential generator (LCG), not Python's random module. This design decision has three motivations: (1) Cross-platform reproducibility — the same seed always produces the same sequence regardless of Python version, operating system, or QGIS installation; (2) No global state — each run creates a fresh LCG instance, so concurrent runs (as in the Seed Gallery) never interfere; (3) Auditability — the 15-line implementation is trivially verifiable.

Mathematical Formulation: The LCG

The LCG uses Knuth's MMIX constants (Knuth, 1997, §3.2.1):

$$s_{n+1} = (a \cdot s_n + c) \bmod m \tag{32}$$

where:

$$\begin{aligned} a &= 6\,364\,136\,223\,846\,793\,005 \\ c &= 1\,442\,695\,040\,888\,963\,407 \\ m &= 2^{64} - 1 = 18\,446\,744\,073\,709\,551\,615 \end{aligned}$$

Initialisation mixes the user seed with the golden ratio constant:

$$s_0 = (\text{seed} \cdot 2 + \texttt{0x9E3779B97F4A7C15}) \bmod m \tag{33}$$

A uniform variate in \([a, b)\) is generated as:

$$U(a, b) = a + (b - a) \cdot \frac{s_n \gg 11}{2^{53}} \tag{34}$$

The right-shift by 11 bits selects only the top 53 bits of the 64-bit state, matching the IEEE 754 double-precision mantissa width. This avoids the well-known weakness of lower-order bits in LCG sequences — the top bits exhibit far better distribution properties.

Field-Name Collision Safety

If the input block layer already has fields named pf_id, pf_area, etc. (from a previous ParcelFlux run on a derived layer), the engine detects the collision and prepends planx_ to the generated field names instead (e.g. planx_pf_id, planx_pf_area). This prevents the engine from overwriting existing attribute data and allows chaining multiple subdivision runs on the same layer.

The collision detection algorithm maintains a set of used field names and for each intended field name, tests candidates in order: (1) the base name, (2) planx_ + base name, (3) planx_ + base name + _3, _4, etc. The first unused candidate is selected. This guarantees uniqueness without arbitrary truncation or abbreviation that could confuse downstream analysis tools.

Cancellation-Safe Background Worker

The subdivision runs on a QThread worker. A Cancel button in the dock panel sets a cancellation flag that the engine checks between blocks. The worker thread is detached on plugin unload — the engine uses cooperative cancellation via _CancelAwareFeedback, a QGIS Processing feedback subclass that mirrors the worker's cancellation state to QGIS native algorithm calls. This means cancelling mid-run stops both the ParcelFlux engine and any in-flight QGIS processing operation (e.g. splitWithLines).

Cancellation architecture. The _CancelAwareFeedback class overrides isCanceled() to call the user's cancel callback before delegating to the superclass. This feedback object is passed to every processing.run() call, so long-running native QGIS algorithms (like splitWithLines on a complex polygon) can be interrupted. The engine also checks the cancel flag between blocks and after each progress emission. When cancellation is detected, a _CancelledRun exception is raised and caught at the top level, returning (None, "Cancelled.").

Handling Irregular & Invalid Blocks

Real-world cadastral data frequently contains invalid geometries (self-intersections, ring-ordering errors) and irregular shapes (L-shaped blocks, blocks with curved boundaries). The engine handles these defensively:

Performance note. Each block triggers multiple QGIS native algorithm calls (OBB, extendLines, splitWithLines). For a study area with hundreds of blocks, run time scales approximately linearly with block count. Typical throughput on a modern machine: ~50–100 blocks per second. The Selected only checkbox lets you prototype on a subset before committing to a full run.

Interpretation Guide: Advanced Topics

Seed reproducibility for regulatory submissions. For formal planning submissions where parcel layouts must be reproducible, record the seed value alongside the parameter set. The deterministic LCG guarantees that the same seed and parameters always produce identical parcel geometries — essential for public consultation and regulatory review where "the layout changed between versions" is unacceptable.

Chaining runs. The field collision safety enables a workflow where you (1) run ParcelFlux on raw blocks to produce initial parcels, (2) manually edit parcel boundaries in QGIS for specific design interventions, (3) re-run ParcelFlux on the edited layer with different parameters. The second run's fields receive planx_ prefixes, preserving both the original and new metrics for comparison. Use QGIS's field calculator to compute the difference between pf_area and planx_pf_area to quantify the impact of parameter changes.

Headless batch processing. For scenario analysis, wrap ParcelFluxCore in a Python script that iterates over parameter ranges:

from algorithms.parcel_flux_core import ParcelFluxCore
runner = ParcelFluxCore()
for w in [12, 14, 16, 18, 20]:
    layer, err = runner.run(blocks_layer, lot_width=w)
    if layer:
        QgsVectorFileWriter.writeAsVectorFormatV3(
            layer, f"parcels_w{w}.gpkg", ...)

This enables systematic exploration of the parameter space — for example, generating the full trade-off curve between parcel count and average parcel area for a given block configuration.

Appendix A: Parameter Quick Reference

ParameterDefaultMinMaxStep
lot_width16.0 m8500.5
min_area300.0 m²50200010
max_area2000.0 m²2001000050
merge_threshold35.0%101005
width_variation0%0501
fishbone_offset0.0 m0100.1
row_width_asymmetry0%0501
hline_offset0%0501
min_frontage0.0 m0500.5
parcel_depth0.0 m02001
corner_widening0%01005
seed4202³¹−11

Appendix B: Glossary

TermDefinition
Block (zoning block)A polygon representing a city block or development parcel bounded by streets. The input unit of subdivision.
Centreline (H-line)The line dividing a double-row block into two opposing rows of parcels. Runs along the block's long axis.
Corner parcelThe first or last parcel on a block face, adjacent to the street intersection. Optionally widened via corner_widening.
Coverage percentageThe ratio of output parcel area to input block area, expressed as a percentage. Should always equal 100% — any less indicates data loss.
Cross-widthThe length of the longer of the two shortest edges of a block polygon. Represents the block depth available for parcel rows.
Division lineA perpendicular line drawn across the block at each lot-width interval. These lines become the shared boundaries between adjacent parcels.
Double-rowA block wide enough to accommodate two opposing rows of parcels, back-to-back, separated by a centreline.
Fishbone offsetA random perpendicular displacement at division-line endpoints, producing a zigzag boundary pattern reminiscent of organic settlement fabrics.
H-line offsetDisplacement of the centreline away from the geometric midpoint, creating asymmetric front/rear parcel depths.
LCG (Linear Congruential Generator)A deterministic pseudo-random number generator. ParcelFlux uses a 64-bit LCG (Knuth MMIX constants) with a user-supplied seed for reproducible variation.
OBB (Oriented Bounding Box)The minimum-area rectangle enclosing a polygon, rotated to align with the polygon's principal axes. Used as a layout guide, never as a clipping boundary.
Regular blockA block polygon with exactly 4 distinct vertices (near-rectangular). The OBB is directly usable for subdivision.
Residual mergingThe process of eliminating fragments (slivers, undersized parcels, under-fronted parcels) by merging them into the neighbour with the longest shared edge.
Row width asymmetryDifferent lot widths on the two sides of a double-row block's centreline, producing differentiated front/rear facades.
Single-rowA narrow block subdivided into a single row of parcels spanning the full block width.
SliverA fragment with area < 5.0 m² — effectively unusable. Eliminated in the first merge pass.
Width variationRandom ± percentage modulation of each parcel's lot width, producing non-uniform streetscapes.

Appendix C: Bibliography

Aichholzer, O., Aurenhammer, F., Alberts, D. & Gärtner, B. (1995). "A novel type of skeleton for polygons." Journal of Universal Computer Science, 1(12), 752–761. DOI: 10.3217/jucs-001-12-0752

Chen, Z., Song, P. & Ortner, F.P. (2024). "Hierarchical co-generation of parcels and streets in urban modeling." Computer Graphics Forum (Eurographics), 43(2). DOI: 10.1111/cgf.15013

Kelly, T. (2014). "Unwritten procedural modeling with the straight skeleton." PhD Thesis, University of Glasgow. DOI: theses.gla.ac.uk/4975

Keil, J.M. (2000). "Polygon decomposition." In: Sack, J.R. & Urrutia, J. (eds.) Handbook of Computational Geometry, Elsevier, 491–518. DOI: 10.1016/B978-044482537-7/50012-7

Knuth, D.E. (1997). The Art of Computer Programming, Volume 2: Seminumerical Algorithms. 3rd ed., Addison-Wesley, Reading, MA. (MMIX LCG constants, §3.2.1.)

Lipp, M., Wonka, P. & Wimmer, M. (2008). "Interactive visual editing of grammars for procedural architecture." ACM Transactions on Graphics (SIGGRAPH), 27(3), 102. DOI: 10.1145/1360612.1360701

Moudon, A.V. (1997). "Urban morphology as an emerging interdisciplinary field." Urban Morphology, 1(1), 3–10. DOI: 10.51347/jum.v1i1.4047

Müller, P., Wonka, P., Haegler, S., Ulmer, A. & Van Gool, L. (2006). "Procedural modeling of buildings." ACM Transactions on Graphics (SIGGRAPH), 25(3), 614–623. DOI: 10.1145/1141911.1141931

Parish, Y.I.H. & Müller, P. (2001). "Procedural modeling of cities." Proceedings of the 28th Annual Conference on Computer Graphics and Interactive Techniques (SIGGRAPH), 301–308. DOI: 10.1145/383259.383292

Vanegas, C.A., Aliaga, D.G., Wonka, P., Müller, P., Waddell, P. & Watson, B. (2010). "Modelling the appearance and behaviour of urban spaces." Computer Graphics Forum, 29(1), 25–42. DOI: 10.1111/j.1467-8659.2009.01535.x

Vanegas, C.A., Kelly, T., Weber, B., Halatsch, J., Aliaga, D.G. & Müller, P. (2012). "Procedural generation of parcels in urban modeling." Computer Graphics Forum (Eurographics), 31(2pt3), 681–690. DOI: 10.1111/j.1467-8659.2012.03047.x

Watson, B., Müller, P., Wonka, P., Sexton, C., Veryovka, O. & Fuller, A. (2008). "Procedural urban modeling in practice." IEEE Computer Graphics and Applications, 28(3), 18–26. DOI: 10.1109/MCG.2008.58

Wickramasuriya, R., Chisholm, L.A., Puotinen, M., Gill, N. & Klepeis, P. (2011). "An automated land subdivision tool for urban and regional planning: Concepts, implementation and testing." Environmental Modelling & Software, 26(12), 1675–1684. DOI: 10.1016/j.envsoft.2011.06.003

Zhang, M., Wu, J., Liu, Y., Zhang, J. & Li, G. (2022). "GIS based procedural modeling in 3D urban design." ISPRS International Journal of Geo-Information, 11(10), 531. DOI: 10.3390/ijgi11100531


ParcelFlux — Academic Reference Manual · v0.3.1
Yusuf Eminoğlu · August 2026 · GitHub