Ben Traje
← Back to specialized

How to Automate Cleaning Vector Map Tiles/Labels in QGIS Using PyQGIS

25 Sep 26 (1mo ago)

The Challenge of Messy Vector Maps

When styling vector maps for design projects, map layers imported from sources like OpenStreetMap (OSM) or MapTiler often come loaded with an overwhelming amount of data. While having every residential street, alleyway, and Point of Interest (POI) is great for navigation, it creates a cluttered, spiderweb-like appearance when you only need a clean graphical map of main arteries.

While vector editing tools like Affinity Designer are excellent for final artistic touches, they lack native scripting APIs to batch-process these layers efficiently. Attempting to manually hide thousands of paths using "Select Same" workarounds is tedious.

The most efficient solution is to process the data directly in QGIS using Python (PyQGIS) before exporting your final vector file.

Automating Vector Tile Clean-Up in QGIS

Unlike standard shapefiles where you can apply a simple SQL subset string, Vector Tiles (like .mvt or MapTiler layers) rely on complex styling engines. You have to programmatically disable specific style rules and labeling engines.

Below is a comprehensive PyQGIS script designed to loop through your active vector tile layer and turn off minor roads, POIs, and all text labels in one click.

The PyQGIS Script

To use this, select your Vector Tile layer (e.g., MapTiler) in the QGIS layers panel, open the Python Console (Ctrl + Alt + P), and run the following code:

from qgis.core import QgsVectorTileLayer, QgsVectorTileBasicLabeling

# --- CONFIGURATION ---
RESET_ALL = False         # True = Turn everything ON | False = Clean the map
DISABLE_ALL_LABELS = True # True = Hide ALL text | False = Only hide specific label keywords

# Keywords to hide in SYMBOLOGY (Visuals like lines/icons)
minor_keywords = [
    'residential', 'service', 'unclassified', 'tertiary', 
    'footway', 'track', 'path', 'cycleway', 'pedestrian', 
    'living_street', 'minor', 'ferry',
    'poi', 'place', 'shield', 'aerodrome', 'airport'
]
# ---------------------

layer = iface.activeLayer()

if isinstance(layer, QgsVectorTileLayer):
    print(f"--- Processing Layer: {layer.name()} (Reset: {RESET_ALL}) ---")
    
    # ==============================
    # PART 1: SYMBOLOGY (Lines/Icons)
    # ==============================
    renderer = layer.renderer()
    styles = renderer.styles()
    new_styles = []

    for style in styles:
        s_name = style.styleName() if style.styleName() else ""
        s_layer = style.layerName() if style.layerName() else ""
        s_filter = style.filterExpression()
        check_string = (s_name + " " + s_layer + " " + s_filter).lower()
        
        if RESET_ALL:
            style.setEnabled(True)
        else:
            is_minor = any(k in check_string for k in minor_keywords)
            style.setEnabled(not is_minor) # Enable if NOT minor

        new_styles.append(style)

    renderer.setStyles(new_styles)
    print("Symbology updated.")

    # ==============================
    # PART 2: LABELS (Text)
    # ==============================
    labeling = layer.labeling()
    
    # Check if labeling is active and is the standard type
    if labeling and isinstance(labeling, QgsVectorTileBasicLabeling):
        l_styles = labeling.styles()
        new_l_styles = []
        
        for l_style in l_styles:
            if RESET_ALL:
                l_style.setEnabled(True)
            elif DISABLE_ALL_LABELS:
                l_style.setEnabled(False) 
            
            new_l_styles.append(l_style)
            
        labeling.setStyles(new_l_styles)
        layer.setLabeling(labeling) # Apply changes back to layer
        print("Labels updated.")
    else:
        print("No active labeling found or incompatible labeling type.")

    # ==============================
    # REFRESH
    # ==============================
    layer.triggerRepaint()
    iface.layerTreeView().refreshLayerSymbology(layer.id())
    print("--- Map Cleaned ---")

else:
    print("Error: Please select the MapTiler/Vector Tile Layer first.")

How It Works

  • Symbology Targeting: The script extracts the styleName, layerName, and filterExpression from the renderer. It cross-references these attributes against a list of keywords (like residential, poi, and shield) to safely disable only the visual clutter while strictly enforcing that main arteries remain active.
  • Label Nullification: Because QGIS processes text separately from vector lines, the script accesses the QgsVectorTileBasicLabeling class. By toggling DISABLE_ALL_LABELS = True, it instantly unchecks every rule-based label in the layer properties.
  • Non-Destructive: By toggling RESET_ALL = True and running the script again, you can instantly recover all hidden data, making it highly flexible for iterative map design.