ParcelFlux — Algorithm & Parameter Guide

Version: 0.1.0  |  Author: Yusuf Eminoglu  |  License: GPL-3.0

ParcelFlux subdivides zoning blocks (imar adalar) into individual parcels with tunable geometric variation. It operates on polygon layers and produces a memory layer of parcel polygons with area and optional facade-tracking columns.


1. Algorithm Overview

The engine processes each input block polygon independently through six stages:

  1. Geometry Normalization — irregular polygons are replaced by their oriented minimum bounding box (OBB). Regular 4-vertex polygons pass through unchanged.
  2. Axis Detection — the two shortest edges define the block "ends." The line connecting their midpoints becomes the centerline (h-line). The perpendicular direction is the division axis along which parcels are laid out.
  3. Row Detection — if the short-edge width is less than 1.8 * lot_width, the block is treated as single-row (one strip of parcels). Otherwise it is double-row (two opposing strips on either side of the centerline).
  4. Division Line Generation — perpendicular lines are placed along the centerline at intervals equal to lot_width, with optional variation (width, fishbone, asymmetry, h-line shift).
  5. Split & Merge — the block is split by the division lines using native:splitwithlines. Residual fragments below the area threshold are merged into the neighbor with the longest shared edge, in up to 3 passes.
  6. Filter & Output — parcels exceeding max_area or below 5 m² are excluded. Remaining parcels are written to a memory layer with area and optional ID/facade columns.

2. Parameter Reference

2.1 Lot Width lot_width

Default: 16.0 m  |  Range: 5.0 – 100.0 m

Target frontage width of each parcel. This is the primary control over parcel count. Smaller values produce more parcels per block; larger values produce fewer, wider parcels.

n_parcels_per_row ≈ L_centerline / lot_width

where L_centerline is the length of the line connecting the midpoints of the two shortest edges. In double-row mode, each side independently computes its parcel count.

2.2 Minimum Area min_area

Default: 300.0 m²  |  Range: 0.0 – 999999.0 m²

Parcels smaller than this threshold are merged into the adjacent neighbour with the longest shared boundary. This operates alongside the merge-threshold rule. Set to 0 to disable hard-minimum enforcement.

2.3 Maximum Area max_area

Default: 2000.0 m²  |  Range: 0.0 – 999999.0 m²

Parcels exceeding this area are filtered out of the output. Use a high value (e.g. 999999) to keep all parcels regardless of size.

2.4 Merge Threshold merge_threshold

Default: 35.0 %  |  Range: 0.0 – 100.0 %

Residual (sliver) merging threshold expressed as a percentage of the mean parcel area after the current pass. The threshold is recomputed after each merge pass:

T_pass = (merge_threshold / 100) * (Σ area_i / n_parcels)

Any parcel with area < T_pass is merged into its neighbor with the longest shared edge. Three passes are attempted; the process stops early if no parcels are merged in a pass. Higher values = more aggressive merging. 0 = only the min_area rule applies.

2.5 Uniform Corners uniform_corners

Default: enabled (True)

Controls how leftover space along the centerline is distributed:

offset_per_side = (L_centerline - n * lot_width) / 2 [uniform=True]
offset_per_side = 0 [uniform=False]

2.6 Width Variation width_variation

Default: 0.0 %  |  Range: 0.0 – 25.0 %

Per-parcel random width variation. Each parcel in a row receives an independent random multiplier:

w_i = lot_width * (1 + U(-var, +var) / 100)
where U(a,b) is a uniform random draw in [a,b]

After randomization, all widths are re-normalized so their sum equals the available centerline length. At 0%, all parcels in a row are identical. At 15% with a 20 m target, parcels range from approximately 17 m to 23 m.

Seeded randomness: the random number generator is seeded with a fixed value (42) to produce reproducible results. Run the tool twice on the same input with the same parameters and you will get identical output.

2.7 Fishbone Offset fishbone_offset

Default: 0.0 %  |  Range: 0.0 – 15.0 %

Organic zigzag shift applied to division-line endpoints along the centerline direction. Creates a herringbone pattern that mimics traditional settlement morphologies.

offset_max = lot_width * (fishbone / 100)
offset_left = U(-offset_max, +offset_max)
offset_right = U(-offset_max, +offset_max)
P_left = P_center + offset_left * centerline_direction
P_right = P_center + offset_right * centerline_direction

At 0%, division lines are straight. At 5-10%, the herringbone effect becomes visually apparent. The two endpoints of each division line are offset independently, so lines can slant rather than remaining parallel.

2.8 Row Width Asymmetry row_width_asymmetry

Default: 0.0 %  |  Range: 0.0 – 25.0 %

Creates different parcel widths on the two rows on opposite sides of the centerline. This models real-world patterns where north-facing parcels may be wider (more desirable solar orientation) than south-facing ones.

shift = U(-asym, +asym) / 100
width_side_A = lot_width * (1 + shift)
width_side_B = lot_width * (1 - shift)
Example: lot_width=20m, asymmetry=10% Row A (north-facing): ~22m-wide parcels (1 + 0.10) * 20 Row B (south-facing): ~18m-wide parcels (1 - 0.10) * 20 Block short edge = 44m, so 2 parcels on A (2*22) and 2.44 parcels on B (44/18), i.e. 2 on B with remainder merged.

This parameter is ignored in single-row mode (when the block's short edge < 1.8 * lot_width).

2.9 H-Line Shift hline_offset

Default: 0.0 %  |  Range: 0.0 – 25.0 %

Shifts the center division line away from the geometric midpoint of the block's short edges, producing asymmetric front/rear depths.

h_ratio = 0.5 + U(-off, +off) / 100
P_mid = P_start + h_ratio * (P_end - P_start)

At 0%, the block is split exactly in half. At 10%, the h-line is randomly placed between 40%-60% along each short edge. One row gets deeper parcels, the other gets shallower ones. This is useful for differentiating front-garden and back-garden depths.


3. Single-Row vs Double-Row Detection

The decision rule is:

is_single_row = (max(short_edge_1, short_edge_2) < 1.8 * lot_width)

Rationale for 1.8: each parcel row needs at least lot_width depth. A factor of 1.8 provides a 10% margin (two rows need ~2.0x, thresholding at 1.8 ensures rows are not too shallow). A block with short edge = 30 m and lot_width = 16 m triggers double-row mode because 30 ≥ 1.8 * 16 = 28.8.


4. Residual Merging Algorithm

After the split, sliver polygons (triangular fragments, narrow strips) inevitably form near block edges and division-line intersections. The 3-pass merging algorithm handles these:

  1. Pass 1: compute mean parcel area. Any parcel below max(threshold_pct * mean, min_area) is a candidate.
  2. For each candidate, find the spatial neighbor with the longest shared boundary (measured via intersection().length() on LineString intersections).
  3. Merge the candidate into that neighbor via geometry.combine().
  4. Repeat passes 2 and 3 with a recalculated threshold (mean parcel area changes after each pass).
  5. Stop early if no parcels are merged in a pass.

This is a greedy algorithm — it processes candidates in feature-ID order and does not revisit already-merged parcels within a single pass. The 3-pass structure allows cascading merges: a merged parcel from pass 1 may itself be below threshold in pass 2 and get merged again.


5. Output Schema

ColumnTypeDescription
Copied from input layer
block_id *StringSource feature ID of the parent block
Computed columns
parcel_id *IntegerSequential parcel number (1..N)
area_m2DoubleParcel area in square metres
Facade tracking columns * (placeholders)
facade_front *StringFront facade edge description (empty)
facade_side *StringSide facade edge description (empty)
facade_back *StringBack facade edge description (empty)
facade_count *IntegerNumber of facade edges (0)
is_corner *BooleanCorner-parcel flag (False)
front_direction *StringCardinal direction hint (empty)

* = optional; controlled by "Add ID columns" and "Add facade columns" checkboxes in the dock UI.


6. QGIS Compatibility

VersionStatus
QGIS 3.28Minimum supported
QGIS 3.34 LTRSupported
QGIS 3.40 LTRSupported
QGIS 4.xSupported (tested with qgisMaximumVersion=4.99)

The plugin uses only stable PyQGIS APIs and native Processing algorithms. The QgsWkbTypes.GeometryType enum is resolved at import time with a try/except fallback for QGIS 3.


7. Typical Workflow

  1. Load your zoning block polygon layer into QGIS.
  2. Open the ParcelFlux dock panel (ParcelFlux menu or toolbar).
  3. Select the block layer.
  4. Set Lot Width to match your zoning regulations (e.g. 16 m for residential, 25 m for villa plots).
  5. Adjust Min/Max Area to filter extreme parcel sizes.
  6. Optionally enable Width Variation (5-10%) for natural-looking diversity.
  7. For north/south differentiation, enable Row Width Asymmetry (5-12%).
  8. For organic-looking boundaries, enable Fishbone Offset (3-8%).
  9. Click RUN.
  10. The output layer is added to the map. Inspect, then run facade labeling (Step 2 in the PlanX Settlement toolset) to populate facade columns.

ParcelFlux 0.1.0 — part of the PlanX Geospatial Tools suite.