Bivariate Cartographic Style

Orion constellation thumbnail

In a previous post I wrote about using the Python Cartographic Library for charting the bright stars. Now I'm revisiting this script while working on a generalized bivariate cartographic style for PCL. Style generator, more precisely, in which one feature property parameterizes M point symbolizer sizes and another parameterizes N colors. From these we generate M x N rules for symbolizing features.

In this example, symbolizer size in pixels is determined by the visible magnitude of a star, and color is determined by spectral type. The coolest M type stars are shown in red, the hottest B type stars in purple. If you're fortunate enough to be somewhere with an optically thin night sky and and few city lights, you can actually see the color difference between the two brightest stars in Orion with an unaided eye. Betelguese (alpha Ori) is the red star at the upper left of Orion, and Rigel (beta Ori) is at the lower right.

The symbolization rules are parameterized by the following mappings of magnitude ranges to pixel sizes, and a function of the spectral type string to a hex color:

mags = [
    ['f.MAGNITUDE <= 0',       13],
    ['0 < f.MAGNITUDE <= 1',   11],
    ['1 < f.MAGNITUDE <= 2',    9],
    ['2 < f.MAGNITUDE <= 3',    7],
    ['3 < f.MAGNITUDE <= 4',    5],
    ['4 < f.MAGNITUDE <= 5',    3]
]

colors = [
    ['f.SPECTRAL_TYPE.startswith("O")', '#FF99FF'],
    ['f.SPECTRAL_TYPE.startswith("B")', '#CC99FF'],
    ['f.SPECTRAL_TYPE.startswith("A")', '#9999FF'],
    ['f.SPECTRAL_TYPE.startswith("F")', '#99FF99'],
    ['f.SPECTRAL_TYPE.startswith("G")', '#FFFF99'],
    ['f.SPECTRAL_TYPE.startswith("K")', '#FF6633'],
    ['f.SPECTRAL_TYPE.startswith("M")', '#FF3300'],
    [True, '#CCCCCC'],
]

The rules are a product of these two lists, and in this case the filter expressions are simply and-ed:

star_style = Style()
for m in mags:
    for c in colors:
        g = Graphic(mark=Mark('circle', fill=Fill(c[1], opacity=1.0),
            stroke=None), size=m[1])
        symb = PointSymbolizer(graphic=g)
        rule = Rule(m[0], [symb], "%s and %s" % (m[0], c[0]))
        star_style.rules.append(rule)

You could accomplish this in MapServer too, but it would require 48 class definitions and at least 500 lines of configuration. In my mind, the code above is a big improvement.