A mapping "not quite how-to"

Share on:

Message about the underlying software

NOTE: much of the material and discussion here uses the Python package "folium", which is a front end to the Javascript package "leaflet.js". The lead developer of leaflet.js is Volodymyr Agafonkin, a Ukrainian up until recently living and working in Kyiv.

Leaflet version 1.80 was released on April 18, "in the middle of war", with "air raid sirens sounding outside". See the full statement here.

Stand With Ukraine

(This banner is included here with the kind permission of Mr Agafonkin.) Please consider the Ukrainian people who are suffering under an unjust aggressor, and help them.

The software libraries and data we need

The idea of this post is to give a little insight into how my maps were made. All software is of course open-source, and all data is freely available.

The language used is Python, along with the libraries:

  • Pandas for data manipulation and analysis
  • GeoPandas which adds geospatial capabilities to Pandas
  • folium for map creation - this is basically a Python front-end to creating interactive maps with the powerful leaflet.js JavaScript library.
  • geovoronoi for adding Voronoi diagrams to maps

Other standard libraries such as numpy and matplotlib are also used.

The standard mapping element is a shapefile which encodes a map element: for example the shape of a country or state; the position of a city. In order to use them, they have to be downloaded from somewhere. For Australian Federal elections, the AEC makes available much relevant geospatial information.

Victorian geospatial information can be obtained from Vicmap Admin.

Coordinates of polling booths can be obtained again from the AEC for each election. For the recent 2022 election, data is available at their Tallyroom. You'll see that this page contains geospatial data as well as election results. Polling booth locations, using latitude and longitude, are available here.

Building a basic map

We can download the shapefiles and polling booth information (unzip any zip files to extract the documents as needed), and read them into Python:

1vic = gpd.read_file("E_VIC21_region.shp")
2booths = pd.read_csv("GeneralPollingPlacesDownload-27966.csv")

Any Division in Victoria can be obtained and quickly plotted; for example Higgins:

1higgins = vic.loc[vic["Elect_div"]=="Higgins']
2higgins.plot()

We can also get a list of the Polling Places in the electorate, including their locations:

1higgins_booths = booths.loc[booths["DivisionNm"]=="Higgins"][["PollingPlaceNm","Latitude","Longitude"]]

With this shape, we can create an interactive map, showing for example the names of each polling booth.

To plot the electorate on a background map we need to first turn the shapefile into a GeoJSON file:

1higgins_json = folium.GeoJson(data = higgins.to_json())

And to plot it, we can find its bounds (in latitude and longitude) and place it in a map made just a little bigger:

 1b0,b1,b2,b3 = higgins.total_bounds
 2extent = 0.1
 3centre = [(b1+b3)/2,(b0+b2)/2]
 4
 5hmap = folium.Map(location=centre,
 6                  min_lat=b1-extent,
 7                  max_lat=b3+extent,
 8                  min_long=b0-extent,
 9                  max_long=b2+extent,
10                  width=800,height=800,
11                  zoom_start=13,
12                  scrollWheelZoom=False
13                  )
14higgins_json.add_to(hmap)
15hmap

The various commands and parameters above should be straightforward: from the latitude and longitude given as bounds, the variable centre is exactly that. Because our area is relatively small, we can treat the earth's surface as effectively flat, and treat geographical coordinates as though they were Cartesian coordinates. Thus for this simple map we don't have to worry about map projections. The defaults will work fine. The variables min_lat and the others define the extent of our map; width and height are given in pixels; and an initial zoom factor is given. The final setting ``scrollWheelZoom=False`` stops the map from being inadvertently zoomed in or out by the mouse scrolling on it (very easy to do). The map can be zoomed by the controls in the upper left:

We can color the electorate by adding a style to the JSON variable:

1higgins_json = folium.GeoJson(data = higgins.to_json(),
2                               style_function = lambda feature: {
3                                   'color': 'green',
4                                   'weight':6,
5                                   'fillColor' : 'yellow',
6                                   'fill': True,
7                                   'fill_opacity': 0.4}
8                              )

Because folium is a front end to the Javascript leaflet.js package, much information is available on that site. For instance, all the parameters available to change the colors, border etc of the electorate are listed in the description of the leaflet Path.

Adding interactivity

So far the map is pretty static; we can zoom in and out, but that's about it. Let's add the voting booths as circles, each one with with a "tooltip" giving its name. A tooltip is like a popup which automatically appears when the cursor hovers over the relevant marker on the map. A popup, on the other hand, requires a mouse click to be seen.

We can create the points and tooltips from the list of booths in Higgins.

1for index, row in higgins_booths.iterrows():
2    loc = [row["Latitude"],row["Longitude"]]
3    tooltip = ("<b>{s1}</b>").format(s1 = row["PollingPlaceNm"])
4    folium.CircleMarker(radius=5,color="black",fill=True,location=loc,tooltip=tooltip).add_to(hmap)

The map can now be saved:

1hmap.save("higgins_booths.html")

and viewed in a web browser:

This a very simple example of an interactive map. We can do lots more: display markers as large circles with numbers in them; divide the map into regions and make the regions respond to hovering or selection, add all sorts of text (even html iframes) as popups or tooltips, and so on.