Skip to content
Will Roper edited this page Feb 26, 2024 · 7 revisions

Draw long lines on top:

click control feature rendering order checkbox, and then click the AZ-> button to the right in style pane, and enter $length in expression editor

draw order

Colouring in...

Make your lines and addresses the same colour, and spin your polling stations to spot when they're on top of each other.

import colorsys
import random

def generate_random_colour():
    # Generate random hue, saturation, and lightness values
    h = random.random()
    s = random.uniform(0.3, 0.9)
    l = random.uniform(0.2, 0.8)

    # Convert HSL to RGB
    r, g, b = colorsys.hls_to_rgb(h, l, s)
    # Scale RGB values to the [0, 255] range
    r, g, b = int(r * 255), int(g * 255), int(b * 255)

    return '%d, %d, %d' % (r, g, b)

def set_address_symbol(colour):
    address_symbol_properties = {
        'name': 'circle',
        'outline_color': '0,0,0,0',
        'outline_style': 'no',
        'size': '1.05',
        'color': colour
    }
    address_symbol = QgsMarkerSymbol.createSimple(
        address_symbol_properties
    )
    return address_symbol
    
def set_line_symbol(colour):
    line_symbol_properties = {
        'line_color': colour,
        'line_width': '0.5'
    }
    line_symbol = QgsLineSymbol.createSimple(
        line_symbol_properties
    )
    line_symbol.setOpacity(0.6)
    return line_symbol

def set_station_symbol(colour):
    x_symbol = QgsMarkerSymbol.createSimple({
        'name': 'cross_fill',
        'angle': '45',#f'{randrange(0, 90)}', 
        'color': colour,
        'outline_color': 'black', 
        'outline_style': 'solid', 
        'outline_width': '0.4',
        'size': '6'
    })
    return x_symbol

def update_categories(categories, symbol, fid):
    category = QgsRendererCategory(fid, symbol, str(fid))
    categories.append(category)

def order_layer_by_length(renderer):
    renderer.setOrderByEnabled(True)
    renderer.setOrderBy(
        QgsFeatureRequest.OrderBy(
            [QgsFeatureRequest.OrderByClause("$length")]
        )
    )
    return renderer
    

def apply_categories_render_to_layer(layer, categories, field):
    renderer = QgsCategorizedSymbolRenderer(field, categories)
    if layer.name() == 'pollingstations_lines_view':
        renderer = order_layer_by_length(renderer)
    if renderer is not None:
        layer.setRenderer(renderer)
    layer.triggerRepaint()
    layer.reload()

addresses = QgsProject.instance().mapLayersByName(
    'pollingstations_residential_address_view')[0]
lines = QgsProject.instance().mapLayersByName(
    'pollingstations_lines_view')[0]
stations = QgsProject.instance().mapLayersByName(
    'pollingstations_pollingstation')[0]

fni = stations.fields().indexFromName('internal_council_id')
station_ids = stations.uniqueValues(fni)

address_categories = []
line_categories = []
station_categories = []
    
for station_id in station_ids:
    colour = generate_random_colour()

    address_symbol = set_address_symbol(colour)
    line_symbol = set_line_symbol(colour)
    station_symbol = set_station_symbol(colour)
    
    update_categories(address_categories, address_symbol, station_id)
    update_categories(line_categories, line_symbol, station_id)
    update_categories(station_categories, station_symbol, station_id)


apply_categories_render_to_layer(
    addresses, address_categories, 'polling_station_id'
)
apply_categories_render_to_layer(
    lines, line_categories, 'internal_council_id'
)
apply_categories_render_to_layer(
    stations, station_categories, 'internal_council_id'
)

iface.setActiveLayer(lines)
iface.zoomToActiveLayer()