User:TastyCakes/Map Maker

From Wikipedia, the free encyclopedia

Below is a python script I wrote for automatic colouring of maps. It gets the data from a csv file and colours each geographic entity according to a set of cutoff values defined in a list. At the bottom of this file, it creates maps based on this blank map of US states and this blank map of US counties. Unfortunately, the svg files that this program (specifically, the "tree.write" function) produces are pretty ugly if you just open them up and look at them in a text editor. I made some attempts to make it look better, but it ended up just breaking the code. So if you know how to fix that, please let me know.

I am not a very good programmer, and I'm sure this shows in the code below. Sorry about that...

"""
Version 2: Somewhat more "generalized" than Version 1.  Now you define a set of cutoffs as a string, then the program a blank map, 
a data file and you tell it what rows to look for in the data file.  The actual request to make the map isn't until the bottom,
when two maps are made, one for a state map and one for a county map of the US.
"""
import xml.etree.ElementTree as etree
import csv

def is_number(s): #Returns true if s is a number, false otherwise
    try:
        float(s)
        return True
    except ValueError:
        return False


def GetColour(value, Spectrum, CO):
    """
    This function returns the colour you set to each value.  The function must be told what spectrum to use, and
    what list of cutoff values to use (CO).  CO is a list of 8 cutoff values.
    """

    RedSpectrum = ['#f4d7d7', '#e9afaf', '#de8787', '#d35f5f', '#c83737', '#a02c2c', '#782121', '#501616', '#280b0b'] #A series of increasingly dark reds.
    GreenSpectrum = ['#d7f4d7', '#afe9af', '#87de87', '#5fd35f', '#37c837', '#2ca02c', '#217821', '#165016', '#0b280b'] #Increasingly dark greens.

    if Spectrum == 'Red':
        UseSpectrum = RedSpectrum
    elif Spectrum == 'Green':
        UseSpectrum = GreenSpectrum
    else:
        print("Spectrum not found.  Assuming Red.")
        UseSpectrum = RedSpectrum
    
    if is_number(value):
        import bisect
        return UseSpectrum[bisect.bisect_left(CO, value)]
    print("GetColour Error!")
    return "ERROR"

def ColourMap(BlankMap, OutputMap, DataFile, CutOffs, NameCol = 0, DataCol = 1, Spectrum = 'Red', ZeroPad = 2):
    """
    This function takes DataFile (a csv file with a "name" column and a "data" column), and colours the BlankMap accordingly.
    It outputs OutputMap.
    """

    ifile = open(DataFile)
    reader = csv.reader(ifile)
    Dictionary = {} #The values from the DataFile will be put into this dictionary.

    tree = etree.parse(BlankMap)
    root = tree.getroot()
    ElList = tree.findall('//{http://www.w3.org/2000/svg}path')
    notfound = []

    for row in reader:
        if is_number(row[DataCol]):
            Dictionary[str(row[NameCol]).zfill(ZeroPad).strip()] = GetColour(float(row[DataCol]), Spectrum, CutOffs)
    #print (Dictionary)
    for element in ElList:
        if element.get('id') in Dictionary:
            element.set('style',"fill:" + Dictionary[element.get('id')])
            #print (element.get('id') + ": " + str(Dictionary[element.get('id')]))
        else:
            notfound.append(element.get('id'))

    tree.write(OutputMap)
    print('These elements were not found:')
    for entry in notfound:
        print(entry)

def MakeLegend(CutOffs, Spectrum, Percent = True):
    """
    Makes a legend to cut and paste into Wikipedia for a map.  Input the same CutOffs and Spectrum as the map.  If you do not want a percent
    sign after the values, say "Percent = False" when calling this function.
    """
    Legend = "Legend:\n" + "{{" + "col-begin" + "}}" + "\n" + "{{" + "col-break" + "}}" + "\n"
    RedSpectrum = ['#f4d7d7', '#e9afaf', '#de8787', '#d35f5f', '#c83737', '#a02c2c', '#782121', '#501616', '#280b0b'] #A series of increasingly dark reds.
    GreenSpectrum = ['#d7f4d7', '#afe9af', '#87de87', '#5fd35f', '#37c837', '#2ca02c', '#217821', '#165016', '#0b280b'] #Increasingly dark greens.

    if Spectrum == 'Red':
        UseSpectrum = RedSpectrum
    elif Spectrum == 'Green':
        UseSpectrum = GreenSpectrum
    for x, i in enumerate(UseSpectrum):
        if x < len(CutOffs):
            Legend += "{{" + "legend|" + str(GetColour(CutOffs[x], Spectrum, CutOffs)) + '| <' + str(CutOffs[x])
        else:
            Legend += "{{" + "legend|" + str(GetColour(CutOffs[x-1]+1, Spectrum, CutOffs)) + '| >' + str(CutOffs[x-1])
        if Percent:
            Legend += '%}}\n'
        else:
            Legend += '}}\n'
    Legend += "{{" + "col-end" + "}}" + "\n"
    

    print(Legend)

    


Cutoffs1 = [3.75, 4, 4.25, 4.5, 4.75, 5, 5.25, 5.5]
Cutoffs2 = [5, 10, 15, 20, 25, 30, 35, 40]           
    
#ColourMap('USA_Counties_with_FIPS_and_names.svg', 'US Poverty Rates.svg', 'FIPS Codes just counties.csv', Cutoffs2, NameCol = 0, DataCol = 2, Spectrum = 'Red', ZeroPad = 5)
#ColourMap('Blank_US_Map_with_borders.svg', 'US HDI.svg', 'US states by HDI.csv', Cutoffs1, NameCol = 1, DataCol = 2, Spectrum = 'Red', )

MakeLegend(Cutoffs2, 'Red', Percent = False)
MakeLegend(Cutoffs2, 'Red', Percent = True)