Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/danizd/urbanviable/llms.txt

Use this file to discover all available pages before exploring further.

The vector tile schema is the formal contract between the Python ETL pipeline and the MapLibre GL JS frontend. Every census section feature published in galicia_scouting.mbtiles must expose exactly these 11 properties — no more, no fewer. The ETL (process_data.py) is responsible for computing and writing them; the frontend reads them directly from the tile at render time via GPU expressions. If a column is missing or misnamed, MapLibre silently treats it as undefined (numerically 0), which produces a flat gray map with no visible error.

Schema

cusec
string
required
Ten-digit census section code. Format: CCPP(2) + MMM(3) + DDD(3) + SS(2).
  • CC — province code: 15 (A Coruña), 27 (Lugo), 32 (Ourense), 36 (Pontevedra)
  • PP — combined with CC to form the 2-digit NUTS province
  • MMM — municipality code (3 digits, zero-padded)
  • DDD — district code (3 digits, zero-padded)
  • SS — section number within the district (2 digits)
Example: 1500101001 → A Coruña, municipality 001, district 001, section 01.
renta_norm
float [0, 1]
required
Min-Max normalized net mean household income, computed over all ~3,800 Galician census sections. Source: INE Atlas de Renta. Used by the GPU scoring expression.
renta_abs
integer
required
Net mean household income in euros (non-normalized absolute value). Example: 24500. Displayed in the section tooltip; not used in GPU scoring.
densidad_norm
float [0, 1]
required
Normalized population density (inhabitants per km²), computed over all sections in Galicia using Min-Max normalization. Area in km² is calculated in ETRS89 (EPSG:25830) before reprojection.
jovenes_norm
float [0, 1]
required
Normalized percentage of the section’s population aged under 20. Source: IGE population indicators by census section.
mayores_norm
float [0, 1]
required
Normalized percentage of the section’s population aged over 64. Source: IGE population indicators by census section.
poblacion_abs
integer
required
Total number of inhabitants in the census section. Example: 3200. Displayed in the tooltip; not used in GPU scoring.
actividad_norm
float [0, 1]
required
Log-normalized commercial activity density — the number of OSM commercial establishments per km², passed through log(1 + x) before Min-Max normalization. See Scoring Formula for the full normalization rationale. Source: OpenStreetMap Galicia (Geofabrik).
actividad_abs
integer
required
Raw count of commercial establishments (OSM POIs: shops, restaurants, offices, services) within the census section. Example: 45. Displayed in the tooltip as 45 estab.
uso_comercial_norm
float [0, 1]
required
Normalized ratio of buildings with commercial or office use (5_retail, 4_office, 3_industrial in Catastro coding) to total buildings in the section. Source: Catastro Vectorial, building layer, currentUse field.
antiguedad_norm
float [0, 1]
required
Normalized building modernity — higher values indicate newer building stock. Computed as minmax_norm(mean_construction_year) applied directly to the mean construction year, so sections with more recently built stock score higher. Source: Catastro Vectorial, FECHAALTA / beginning field.

Naming Conventions

Two consistent suffixes are used throughout the schema:
SuffixMeaningRangeUsed by
_normMin-Max (or log) normalized value, dimensionless[0.0, 1.0]GPU scoring expression, MapLibre interpolation
_absAbsolute value in its natural unit (€, inhabitants, establishments)UnboundedTooltip display only
The GPU shader can only efficiently compare values in a fixed range. The _norm columns satisfy this requirement: they are pre-computed at ETL time for the entire Galicia dataset, so the GPU never needs to know the original scale of any variable. The _abs columns exist purely for human-readable display in the sidebar tooltip.

Example Feature Properties

The following JSON object is what MapLibre exposes to paint expressions and click event handlers for a single census section feature:
{
  "cusec": "1500101001",
  "renta_norm": 0.72,
  "renta_abs": 38500,
  "densidad_norm": 0.45,
  "jovenes_norm": 0.38,
  "mayores_norm": 0.61,
  "poblacion_abs": 3200,
  "actividad_norm": 0.54,
  "actividad_abs": 45,
  "uso_comercial_norm": 0.31,
  "antiguedad_norm": 0.58
}
These values are embedded in the .pbf tile binary at tile-generation time by Tippecanoe. They are not fetched from a server on demand — they travel with the tile and are decoded in the browser.
MVP data availability: In the minimum viable product, OSM and Catastro data may not be processed. In that case, actividad_norm, actividad_abs, uso_comercial_norm, and antiguedad_norm are set to 0.0 / 0 for all sections. Their sliders remain visible and functional in the UI, but moving them produces no visual variation in the map because every section scores equally on those dimensions. Full variation becomes visible once those ETL phases are run and the .mbtiles file is regenerated.

Post-ETL Quality Assertions

The following checks are executed inside validate_output() at the end of process_data.py, before Tippecanoe is invoked to generate the .mbtiles file. The pipeline raises a ValueError and aborts if any check fails.
def validate_output(output: gpd.GeoDataFrame) -> None:
    # Minimum section count — catch silent geometry or join failures
    if len(output) < 1500:
        raise ValueError(f"Numero de secciones inesperado: {len(output)}")

    # No duplicate section codes — uniqueness required for click-to-identify
    if output["cusec"].nunique() != len(output):
        raise ValueError("Existen CUSEC duplicados en la salida")

    # All _norm columns must be within [0, 1]
    norm_cols = [c for c in output.columns if c.endswith("_norm")]
    for column in norm_cols:
        if not output[column].between(0, 1).all():
            raise ValueError(f"{column} fuera del rango [0,1]")
The < 1500 threshold is a conservative floor that catches catastrophically failed runs (e.g., a geometry filter that silently drops all sections) without blocking partial runs where not all provinces are available. The zero-percentage diagnostic described in the specs is informational: a high proportion of zeros is expected for OSM/Catastro columns in MVP mode, but unexpected for renta_norm or densidad_norm.

Build docs developers (and LLMs) love