All in One View

Content from Introduction to Foundations


Last updated on 2026-10-17 | Edit this page

Estimated time: 30 minutes

Overview

Questions

  • What do I need to know before starting the other modules?
  • Which tools does this curriculum use, and what is each one for?
  • How do Google Colab notebooks work?

Objectives

  • Describe what Python, Jupyter notebooks, Google Colab and QGIS are used for
  • Open a notebook in Colab, save your own copy and run its cells
  • Choose the right tool for a simple geospatial task
  • Find your way through the modules of the curriculum

Why a foundations module?


The other modules in this curriculum assume a little background: how to run a Python notebook, what a table and a map layer are, and how to load data into QGIS. This module teaches exactly that, from zero. If you have never written code or opened a GIS, start here. If you already know some Python or QGIS, skim the episodes and use the notebooks as a refresher.

The tools


Tool What it is What we use it for
Python A free, general-purpose programming language with excellent libraries for data and maps Reading, cleaning, analyzing and mapping data in a repeatable way
Jupyter notebook A document that mixes text, code and results Every Python lesson in the curriculum
Google Colab A free service that runs Jupyter notebooks in your web browser Running the notebooks without installing anything
QGIS A free, open source desktop GIS Looking at data, styling layers and making finished maps

Python and QGIS are not rivals. Many projects use both: Python to download and prepare data, and QGIS to check it visually and design the final map. Both read the same file formats (GeoJSON, GeoPackage, shapefile, GeoTIFF).

Working in Google Colab


Every notebook link in this curriculum opens in Colab. The first time:

  1. Make sure you are signed in to a Google account.
  2. Click the notebook link. It opens read-only, straight from GitHub.
  3. Choose File > Save a copy in Drive. Your copy keeps your changes; the original stays clean.
  4. Run cells from top to bottom: click a cell and press Shift + Enter.
  5. If Colab warns that the notebook was not written by Google, click Run anyway.

A few things to know:

  • Order matters. Later cells use variables made in earlier cells. If you skip a cell, you will see a NameError. Use Runtime > Run before to catch up.
  • Sessions end. After a while without activity, Colab disconnects and forgets everything in memory. Reconnect and rerun the cells from the top.
  • Files are temporary. Files you create appear in the Files panel on the left and disappear when the session ends. Download anything you want to keep.
  • Data comes from the web. All notebooks read their data straight from a web address, so you never need to download data by hand.
Callout

Do I need to install Python?

No. Colab runs Python on Google’s computers and already includes the libraries used in this curriculum (pandas, matplotlib, GeoPandas). If you prefer to work on your own computer, the Setup page explains how to install Python with Anaconda.

What this module covers


Episode You will learn Notebooks
Python Basics Running cells, variables, text, lists, dictionaries, decisions, loops and functions 2a to 2f
Working with Data Tables with pandas and charts with matplotlib 3a and 3b
Geospatial Python Basics Map layers with GeoPandas, coordinate reference systems, and joining data 4a to 4c
Getting Started with QGIS Loading data from the web, styling layers and making a finished map layout none (QGIS)

Where to go next


After this module, the curriculum follows the life of a research dataset:

  1. Data Management: data models, formats, coordinate systems and planning
  2. Data Collection: repositories, census data, geocoding, imagery and field data
  3. Data Analysis: network analysis, spatial autocorrelation, clustering and NDVI
  4. Data Visualization: charts, map design and accessibility
  5. Data Curation: metadata, documentation, preservation and sharing
  6. Case Studies: food deserts and grocery access, from start to finish
Challenge

Which tool?

Would you reach for Python (in Colab), QGIS, or both?

  1. Download median income for every county in the United States and calculate a new column
  2. Design a printed poster map of a city’s parks with a legend and scale bar
  3. Check whether a layer someone sent you lines up with the roads
  4. Repeat the same analysis every month when new data is published
  1. Python. Downloading thousands of rows and calculating columns is quick to code and easy to repeat.
  2. QGIS. Its print layout is made for finished maps.
  3. QGIS, for a quick visual check over a basemap (Python could check it too).
  4. Python. A notebook can be rerun each month with the new data, giving the same steps every time.
Key Points
  • Python (in Jupyter notebooks on Google Colab) is for repeatable data work; QGIS is for looking at data and making maps
  • Save your own copy of each notebook, and run cells in order with Shift + Enter
  • Colab sessions and files are temporary; data is always read from the web
  • This module prepares you for every other module in the curriculum

Content from Python Basics


Last updated on 2026-10-17 | Edit this page

Estimated time: 150 minutes

Overview

Questions

  • How do I write and run Python code?
  • How does Python store numbers, text and collections of values?
  • How do I make decisions, repeat work and reuse code?

Objectives

  • Run code in a notebook and read error messages
  • Use variables, numbers, strings, lists and dictionaries
  • Write if statements and for loops
  • Write and call your own functions

How this episode works


This page gives a short overview of each idea. The real learning happens in the six notebooks at the end: each takes 15 to 20 minutes, covers one idea, and ends with an exercise and its solution. Work through them in order. The examples use Indiana counties, so by the end you will already be thinking about the kind of data the other modules use.

Running code and reading errors


A notebook has text cells and code cells. Run a code cell with Shift + Enter and its output appears below it. print() shows a value; # starts a comment that Python ignores.

PYTHON

print("Hello, World!")   # text goes in quotes
print(6 * 7)             # numbers do not

Errors are normal. Read the last line of the message first: it names the problem, such as a SyntaxError (Python could not read the line, often a missing quote or bracket) or a NameError (a name Python does not know, often a typo or a skipped cell).

Variables and types


= stores a value under a name. Every value has a type:

Type Examples Used for
int 177513 Whole numbers, such as counts
float 499.8 Decimal numbers, such as areas and rates
str "Tippecanoe", "18157" Text, including identifiers like FIPS codes
bool True, False Yes or no

PYTHON

population = 177513
land_sq_mi = 499.8
density = population / land_sq_mi      # 355.2 people per square mile

Numbers stored as text ("177513") cannot be used in math until you convert them with int() or float(). Real data often arrives this way: the Census API, for example, returns every value as text.

Text (strings)


f-strings build text from values, and string methods clean it:

PYTHON

name = "  tippecanoe county "
clean = name.strip().title()                      # "Tippecanoe County"
print(f"{clean} had {population:,} people")       # "Tippecanoe County had 177,513 people"

Lists and dictionaries


A list holds values in order; positions start at 0. A dictionary holds key: value pairs, so you look values up by name.

PYTHON

counties = ["Marion", "Lake", "Allen"]
print(counties[0], counties[-1])                  # first and last

allen = {"name": "Allen", "fips": "18003", "unemployment": 4.3}
print(allen["unemployment"])

A dictionary of lists (one list per column) and a list of dictionaries (one dictionary per row) are both ways of describing a table. pandas, in the next episode, turns either one into a DataFrame.

Decisions and loops


if, elif and else choose what runs. A for loop repeats a block for every item in a list. The indented lines belong to the block.

PYTHON

for d in [101.4, 548.3, 21.7]:
    if d >= 300:
        print(d, "urban")
    else:
        print(d, "rural or small town")

Functions


A function packages a piece of work under a name so you can reuse it. return sends a result back.

PYTHON

def density(population, area):
    """Return people per unit of area."""
    return population / area

density(177513, 499.8)
Challenge

Challenge 1: Predict the output

What does each line print? Decide first, then run them in a notebook to check.

PYTHON

print(7 / 2)
print(7 // 2)
print("7" + "2")
print([10, 20, 30][1])
  • 3.5: / always gives a decimal number
  • 3: // divides and rounds down
  • 72: joining two strings, not adding numbers
  • 20: positions start at 0, so [1] is the second item
Challenge

Challenge 2: Fix the bug

This code should print the total population of three counties, but it stops with an error. What is wrong?

PYTHON

populations = ["34365", "360412", "79129"]
total = 0
for p in populations:
    total = total + p
print(total)

The populations are stored as text (inside quotes), and Python cannot add text to a number, so it raises a TypeError. Convert each value inside the loop with total = total + int(p), or store the numbers without quotes in the first place.

Key Points
  • Run cells with Shift + Enter, in order, and read the last line of any error first
  • Values have types (int, float, str, bool); convert numbers stored as text before doing math
  • Lists keep values in order (from position 0); dictionaries look values up by key
  • if / elif / else make decisions; for loops repeat work; functions package work for reuse

Notebooks


Work through these in order. Each one opens in Google Colab.

Notebook What you will do
2a. Notebooks, Cells and Printing Run cells, print, write comments, read error messages and edit text cells
2b. Variables, Types and Math Store values, check types, calculate a population density and convert text to numbers
2c. Strings: Working with Text Build text with f-strings, clean messy names and split coordinates
2d. Lists and Dictionaries Pick items from lists, store county records in dictionaries, and see how both become tables
2e. Conditions and Loops Classify counties with if and loop over lists to build results
2f. Functions Write reusable functions with parameters, defaults and return values

Content from Working with Data


Last updated on 2026-10-17 | Edit this page

Estimated time: 70 minutes

Overview

Questions

  • How do I load a table of data into Python?
  • How do I select, filter, sort and summarize rows and columns?
  • How do I turn a table into a clear chart?

Objectives

  • Read a CSV file from the web into a pandas DataFrame
  • Inspect a DataFrame’s size, columns and data types
  • Select columns, filter rows, sort, add calculated columns and summarize groups
  • Make bar charts, histograms and scatter plots with matplotlib, with clear labels

Libraries


Python’s strength for data work comes from libraries: packages of ready-made code that you load with import. Two of them do most of the work in this curriculum:

Library Usual nickname What it does
pandas pd Tables (DataFrames): reading, cleaning, filtering, summarizing, joining
matplotlib plt Charts of every kind

PYTHON

import pandas as pd
import matplotlib.pyplot as plt

The nicknames are a convention: you will see them in almost every tutorial and answer online.

DataFrames


A DataFrame is a table with labeled columns. Each column has one data type, and each row is one record, such as one county. pd.read_csv() reads a file straight from its web address.

PYTHON

url = "https://raw.githubusercontent.com/SpatialTurn/Foundations/main/data/indiana_county_stats.csv"
counties = pd.read_csv(url)
counties.head()          # first five rows
counties.dtypes          # the type pandas guessed for each column

pandas guesses each column’s type from its values. Identifiers such as FIPS codes often come out as numbers. That looks harmless, but it drops leading zeros and breaks joins later, so convert them to text.

The core operations


Task Code
One column counties["pop_2012"]
Several columns counties[["name", "pop_2012"]]
Filter rows counties[counties["unemp_2016"] >= 5.5]
Two conditions counties[(counties["pop_2012"] < 30000) & (counties["unemp_2016"] >= 5)]
Sort counties.sort_values("pop_2012", ascending=False)
New column counties["density"] = counties["pop_2012"] / counties["land_sq_mi"]
Summary statistics counties.describe()
Summarize by group counties.groupby("density_class")["unemp_2016"].mean()

Calculations on a column apply to every row at once, so you rarely need a loop.

Charts


matplotlib has one function per chart type. Choose the chart from the question:

Question Chart Function
How do a few categories compare? Bar chart plt.bar(), plt.barh()
How are the values of one variable spread out? Histogram plt.hist()
Are two variables related? Scatter plot plt.scatter()
How does something change over time? Line chart plt.plot()

PYTHON

plt.hist(counties["unemp_2016"], bins=12)
plt.title("Unemployment across Indiana's 92 counties, 2016")
plt.xlabel("Unemployment rate (%)")
plt.ylabel("Number of counties")
plt.show()

Every chart needs a title and axis labels with units. For very skewed values, such as population density, a logarithmic axis (plt.xscale("log")) makes the pattern visible. The Data Visualization module goes much further into chart and map design.

Challenge

Challenge 1: Read the code

What does this code produce?

PYTHON

counties[counties["unemp_2016"] > 6].sort_values("pop_2012")[["name", "pop_2012"]]

A small table with the name and 2012 population of the counties whose 2016 unemployment rate was above 6%, sorted from the smallest population to the largest. Reading from the inside out: filter the rows, sort them, then keep two columns.

Challenge

Challenge 2: Pick a chart

Which chart would you use for each question?

  1. Are counties with more people per square mile also counties with higher unemployment?
  2. Which five counties have the most people?
  3. Are most counties’ unemployment rates close to the average, or spread out?
  1. A scatter plot of density against unemployment (with a log scale for density)
  2. A bar chart, sorted from largest to smallest
  3. A histogram of unemployment rates
Key Points
  • pandas reads tables from the web into DataFrames with pd.read_csv(url)
  • Check data types first; keep identifiers such as FIPS codes as text
  • Select, filter, sort, calculate and group with a few lines of code, no loops needed
  • Pick the chart that answers the question, and always label it

Notebooks


Notebook What you will do
3a. Working with Tables in pandas Read Indiana county data, filter, sort, add a density column and summarize by class
3b. Plotting with matplotlib Make bar charts, histograms and scatter plots, combine panels and save a figure

Content from Geospatial Python Basics


Last updated on 2026-10-17 | Edit this page

Estimated time: 105 minutes

Overview

Questions

  • How does Python store and map geographic data?
  • What is a coordinate reference system, and why does it matter for measuring?
  • How do I combine a table with a map layer, or two layers with each other?

Objectives

  • Read vector data from the web into a GeoPandas GeoDataFrame and map it
  • Check, convert and correctly label a layer’s coordinate reference system
  • Measure areas and distances in meters
  • Combine data with attribute joins and spatial joins

Vector data and GeoDataFrames


Most social science maps use vector data: points (a store, a survey respondent), lines (a road, a river) and polygons (a county, a census tract). Each feature has a shape and a row of attributes.

GeoPandas stores vector data as a GeoDataFrame: a pandas DataFrame with one extra geometry column. Everything from the previous episode (selecting, filtering, sorting, new columns) still works, and maps are one line away.

PYTHON

import geopandas as gpd

url = "https://raw.githubusercontent.com/SpatialTurn/Foundations/main/data/indiana_counties.geojson"
counties = gpd.read_file(url)
counties.plot(column="unemp_2016", cmap="YlOrRd", legend=True)

gpd.read_file() reads GeoJSON, shapefiles (also zipped ones) and GeoPackages, from a file or a web address. Shade areas by rates (unemployment, density), not raw counts, or big counties will dominate the map simply because they are big.

Coordinate reference systems


Coordinates are only numbers; the coordinate reference system (CRS) says what they mean. Web data is usually in EPSG:4326: latitude and longitude, in degrees. Degrees are fine for storing data, but a degree of longitude shrinks from about 111 km at the equator to about 85 km in Indiana, so areas and distances calculated in degrees are meaningless.

Method What it does When to use it
layer.crs Shows the CRS Always, before combining or measuring layers
layer.to_crs(epsg=26916) Converts the coordinates to another CRS Before measuring, or to match another layer
layer.set_crs(epsg=4326) Labels a layer whose CRS is missing; the numbers do not change Only when the CRS is missing and you know what it should be

For Indiana, EPSG:26916 (UTM zone 16N, in meters) is a good choice for local distances, and EPSG:5070 (USA Contiguous Albers Equal Area) for areas across the state or the country. The Data Management module’s Coordinate Reference Systems episode explains datums and projections in depth.

Joining data


Join Matches rows by GeoPandas
Attribute join A shared ID, such as a FIPS code or GEOID layer.merge(table, on="fips")
Spatial join Location: which polygon contains each point gpd.sjoin(points, polygons, predicate="within")

The most common reason an attribute join fails is that the ID is text in one table and a number in the other. Convert both to text (and pad with zfill() to the right length) before joining, and always compare the row counts before and after. A spatial join needs both layers in the same CRS.

Challenge

Challenge 1: set_crs or to_crs?

  1. A shapefile arrives with no CRS. Its metadata says the coordinates are latitude and longitude (WGS 84).
  2. You need the distance in meters between two stores stored in EPSG:4326.
  3. One layer is in EPSG:4326 and another in EPSG:26916, and you want to draw them together.
  1. set_crs(epsg=4326): the CRS is missing and you know what it is, so you only add the label.
  2. to_crs(epsg=26916) first, then measure: the coordinates must be converted to meters.
  3. to_crs() one layer to match the other, for example layer2.to_crs(layer1.crs).
Challenge

Challenge 2: Why did the join fail?

You join a table of tract statistics to tract boundaries on GEOID, and every row of the result has empty statistics. What would you check first?

Check the type and format of GEOID in both tables. A common cause is that one table stores it as text ("18157010400") and the other as a number (18157010400), or that leading zeros were lost (a California tract "06001400100" read as the number 6001400100). Convert both to text with .astype(str).str.zfill(11) (11 characters for tracts) and join again. Also check that both tables cover the same year, because tract boundaries change between censuses.

Key Points
  • A GeoDataFrame is a pandas DataFrame plus a geometry column; gpd.read_file() reads it from the web
  • Check .crs first; use to_crs() to a projected CRS in meters before measuring, and set_crs() only to label missing CRS information
  • Attribute joins match IDs (make them the same type first); spatial joins match locations (same CRS first)

Notebooks


Notebook What you will do
4a. Vector Data with GeoPandas Read Indiana counties and Lafayette food stores, make a choropleth and layer points over polygons
4b. Coordinate Reference Systems Compare areas in degrees and meters, measure a distance, and see what a wrong CRS label does
4c. Joining Tables and Layers Fix a failing FIPS join, then find which stores lie inside the city limits

Content from Getting Started with QGIS: Your First Map


Last updated on 2026-10-17 | Edit this page

Estimated time: 105 minutes

Overview

Questions

  • How do I load spatial data into QGIS, including directly from the web?
  • How do I explore a layer’s attributes?
  • How can I style point data by category and polygon data by value?
  • How do I create and export a finished map layout?

Objectives

  • Add a basemap and load a vector layer from a web address
  • Explore a layer’s attribute table
  • Style points with categorized symbology
  • Build a choropleth map with graduated symbology and compare classification methods
  • Create a print layout with title, legend, scale bar, north arrow and source, and export it

Before you start


You need QGIS installed. If you have not installed it yet, or the interface is new to you, follow the Setup page first. These steps were written for the QGIS 3.x Long Term Release; menus may differ slightly in other versions.

We will use two small datasets hosted with this lesson. You do not need to download anything: QGIS can read a file directly from its web address.

Dataset Web address Contents
Alaska airports (points) https://raw.githubusercontent.com/SpatialTurn/Foundations/main/data/alaska_airports.geojson 76 airports with name, use (civilian, military and so on) and elevation. From the QGIS sample dataset.
Indiana counties (polygons) https://raw.githubusercontent.com/SpatialTurn/Foundations/main/data/indiana_counties.geojson 92 counties with pop_2012, land_sq_mi, density (people per square mile) and unemp_2016 (percent)

Part 1: Add a basemap


A basemap gives your data geographic context.

  1. In the Browser panel (usually on the left), expand XYZ Tiles.
  2. Right-click OpenStreetMap and choose Add Layer to Project.
  3. A world map appears in the map canvas.

If you do not see the Browser panel, open it with View > Panels > Browser.


Part 2: Load a layer from the web


  1. Go to Layer > Add Layer > Add Vector Layer.
  2. Under Source Type, choose Protocol: HTTP(S), cloud, etc.
  3. Leave Type as HTTP/HTTPS/FTP.
  4. In URI, paste the airports web address from the table above.
  5. Click Add, then Close.

The 76 airports appear as points over Alaska, and a layer called alaska_airports appears in the Layers panel.

  1. If the points are hidden under the basemap, drag alaska_airports above OpenStreetMap in the Layers panel. Layers at the top of the list are drawn on top.
  2. Right-click the airports layer and choose Zoom to Layer.
Callout

Loading local files

To load a file saved on your computer instead, keep Source Type as File and browse to it. For a shapefile, select the .shp file; the .dbf, .shx, .prj and .cpg files with the same name must be in the same folder.

Save your project now (Project > Save, which creates a .qgz file) and save regularly as you work.


Part 3: Explore the attributes


  1. Right-click the airports layer and choose Open Attribute Table.
  2. You should see 76 rows, one per airport, with the columns name, use and elevation.
  3. Click the use column header to sort it. How many airports are military?

The attribute table holds the data behind the map. Every styling choice you make next is based on one of these columns.

You can also click a single airport with the Identify Features tool (the “i” icon on the toolbar) to see its attributes.


Part 4: Style the points


First, give the airports a clearer symbol than the default dot.

  1. Right-click the airports layer and choose Properties, then the Symbology tab.
  2. With Single Symbol selected at the top, scroll the symbol library at the bottom and choose a marker such as topo airport. Set the Size to about 5 millimeters.
  3. Click Apply.
Screenshot of the QGIS Layer Properties dialog for the airports layer on the Symbology tab. Single Symbol is selected at the top, the marker is an airplane SVG symbol called topo airport, color is black, opacity 100 percent and size 5 millimeters. The symbol library below shows topo airport, topo camp, topo hospital and topo pop capital.
Layer Properties, Symbology tab, with the topo airport marker selected

Now color the airports by how they are used:

  1. Change Single Symbol to Categorized.
  2. Set Value to use.
  3. Click Classify. Each airport type gets its own color.
  4. Double-click a symbol to change its color or size. Use clearly different colors (or shapes) for the four types.
  5. Click OK.

Each renderer is suited to a type of data:

Renderer Use for Example
Single Symbol Showing where things are All airports in one style
Categorized Categories (no order) Airport use
Graduated Numbers (low to high) County density

To rename the layer, as it will appear in the legend, open Properties > Source and change the Layer name (for example, to “Airports by use”).


Part 5: Build a choropleth map


Now load the Indiana counties and shade them by population density.

  1. Load the Indiana counties web address the same way as in Part 2.
  2. Open Properties > Symbology and change Single Symbol to Graduated.
  3. Set Value to density.
  4. Choose a Color ramp such as Blues or Viridis.
  5. Set Mode to Natural Breaks (Jenks) and Classes to 5.
  6. Click Classify, then Apply (not OK yet).
  7. Change Mode to Equal Interval, click Classify and Apply. Then try Equal Count (Quantile). Watch how the map changes.

Population density in Indiana is very skewed: most counties have fewer than 200 people per square mile, and Marion County (Indianapolis) has over 2,000. With Equal Interval, almost every county lands in the lightest class. Choose the mode that best shows your message, and click OK.

Callout

Why density and not population?

The layer also has pop_2012, a count. Shading areas by a count mostly shows where big counties or big cities are. Density (people per square mile) is a rate, so it can be compared fairly between counties of different sizes. See the Normalization section of the Data Visualization module’s Fundamentals of Map Design episode.


Part 6: Create a map layout


To export a finished map with all its elements, use a Print Layout.

  1. Go to Project > New Print Layout and give it a name.
  2. Add the map: choose Add Item > Add Map, then click and drag a rectangle on the page.
  3. Add a title: choose Add Item > Add Label, drag a box above the map, and type your title in the Item Properties panel. Make it large.
  4. Add a legend: choose Add Item > Add Legend and drag a box. In Item Properties, you can rename entries and remove the basemap from the legend.
  5. Add a scale bar: choose Add Item > Add Scale Bar. Set the units to miles or kilometers.
  6. Add a north arrow: choose Add Item > Add North Arrow.
  7. Add a source line: add another label at the bottom with the data source and your name, for example “Data: U.S. Census Bureau, BLS. Map by A. Student, 2026.”
Screenshot of the QGIS Print Layout window titled Airports in Alaska. The Add Item menu is open, listing Add Map, Add 3D Map, Add Picture, Add Label, Add Dynamic Text, Add Legend, Add Scale Bar, Add North Arrow, Add Shape and other items. The page shows a map frame containing Alaska with airport symbols over an OpenStreetMap basemap.
Print Layout with the Add Item menu open

Use Item Properties to change fonts, sizes and frames. Use the Move Item Content tool to pan the map inside its frame without moving the frame.


Part 7: Export


  1. In the layout window, choose Layout > Export as Image (PNG) or Layout > Export as PDF.
  2. Choose a file name and location and accept the default options.
  3. Save the project again (.qgz). The layout is saved inside the project.
A finished QGIS map titled Airports in Alaska, USA. Black airplane symbols mark 76 airports across Alaska over an OpenStreetMap basemap that also shows western Canada. A legend lists Alaska Airports and OpenStreetMap, with a north arrow and a scale bar from 0 to 500 kilometers. Author and date appear below the map.
An example finished layout: airports in Alaska

This example uses a single airport symbol. Your version will also show the airport types from Part 4. Notice that it is missing one element from our list: a data source line.


Common beginner mistakes


  • Forgetting to save the project
  • Mapping counts instead of rates on a choropleth map
  • Leaving layer names such as indiana_counties in the legend
  • Using too many classes or a rainbow color ramp
  • Leaving out the source line, legend or title
Callout

Keep it simple

Start with a clear, simple map. Add complexity only when it helps the reader.


Hands-on exercise


Challenge

Challenge 1: An unemployment map of Indiana

Using the Indiana counties layer, create and export a finished map of 2016 unemployment rates (unemp_2016).

  1. Use Graduated symbology with 5 classes and a sequential color ramp.
  2. Try two classification modes and pick the one that shows the pattern best.
  3. Build a layout with a title that states what the map shows, a legend with units (%), a scale bar, a north arrow and a source line.
  4. Export it as a PNG.
  • Value: unemp_2016. Color ramp: a single hue such as Oranges or Blues (light for low, dark for high).
  • Mode: unemployment ranges only from about 3.1% to 6.6% and is not very skewed, so Natural Breaks or Equal Interval both give readable maps. Quantile also works if the message is about rankings.
  • Legend: rename the layer to something like “Unemployment rate, 2016 (%)”.
  • Title: for example, “Unemployment was highest in northwestern and southern Indiana in 2016”. Check your own map before writing a conclusion like this.
  • Source: “Data: U.S. Bureau of Labor Statistics, 2016.”
Challenge

Challenge 2: Fix the projection (optional)

Alaska looks stretched over the OpenStreetMap basemap, which uses Web Mercator. Change the project’s coordinate reference system to one designed for Alaska and compare.

Click the EPSG code in the bottom-right corner of the QGIS window (or Project > Properties > CRS), search for 3338 (NAD83 / Alaska Albers), select it and click OK. The map is redrawn in an equal area projection for Alaska, and the state no longer looks enormously stretched. The basemap is reprojected too, although its labels may look slightly blurred.

Callout

Layer CRS and project CRS

Every layer has its own coordinate reference system (CRS), shown under Layer Properties > Information. The project also has a CRS, shown in the bottom-right corner of the window. QGIS reprojects layers on the fly so that they line up on screen, which is convenient but can hide a layer with a wrong or missing CRS. Two habits avoid most problems:

  • Before measuring distances or areas, set the project CRS (and reproject layers you will analyze) to a projected system in meters, such as EPSG:26916 (UTM zone 16N) for Indiana.
  • If a layer appears in the wrong place, check its CRS before anything else.

Notebook 4b in this module shows the same ideas in Python, and the Data Management module’s Coordinate Reference Systems episode explains the theory.


Final takeaways


  • QGIS is a free, powerful tool for designing maps.
  • Layers can be loaded from files on your computer or directly from a web address.
  • Categorized symbology suits categories; graduated symbology suits numbers.
  • The print layout is where map elements (title, legend, scale bar, north arrow, source) come together.
Discussion
  • What challenges did you face while making your first map?
  • How would you change your map for a different audience?
Key Points
  • Add Vector Layer with the HTTP(S) protocol loads data straight from a web address.
  • The attribute table holds the data behind every styling choice.
  • Use Categorized for categories and Graduated for numbers, and compare classification modes.
  • A Print Layout combines the map with its title, legend, scale bar, north arrow and source.
  • Save the project often; export layouts as PNG or PDF.
  • Check layer and project coordinate systems before measuring anything.