Lab Week 09
Overview
This lab is your first hands-on session for Assignment 3. You will be working in pairs for the rest of the course. By the end of today you should have the following.
- A package skeleton on disk and on GitHub.
- A
pyproject.tomlthat builds without errors. - A first working function with a docstring.
- A design report (with images or diagrams) that you and your partner agree on.
What you need.
- The Assignment 3 brief assigned to your pair (see 4.5 Package project briefs).
uvinstalled and working.- A shared GitHub repository for your pair (you will create it during the lab).
Both partners commit code to the shared repo. Both partners must be able to explain every part of the package by Week 10 (this matters for the fit interview held in next week’s lab). Split the work, but review each other’s commits.
Part 1. Read the brief together
- Open your assigned brief in 4.5.
- Underline the three required functions. Write the signatures on a sheet of paper or in a shared document.
- Underline the demonstration dataset. Note the source URL.
- Identify one part of the brief you are unsure about and ask the demonstrator before moving on.
Part 2. Create the package skeleton together
One partner creates a new GitHub repository for your pair (private or public, your choice). Add the other partner as a collaborator.
Clone it locally and run the following.
cd your-package-name
uv init --lib --name your-package-name .Replace your-package-name with a hyphenated name based on your brief (for example, pedestrian-exposure-uoa, equitransport-akl, escooter-akl-yourinitials). Names on PyPI must be unique, so add initials or a suffix.
- Open
pyproject.toml. Update thename,description, andauthorsfields. Both partners go inauthors.
[project]
name = "your-package-name"
description = "A short description matching your brief."
authors = [
{ name = "Partner A", email = "a@example.com" },
{ name = "Partner B", email = "b@example.com" },
]- Build to confirm everything is wired correctly.
uv buildIf the build fails, read the first error message carefully and fix it before continuing. Common causes include a missing [project] section or a name that does not match the folder under src/.
Part 3. Sketch the public API
- Open
src/your_package_name/__init__.py(folder name uses underscores). Replace the contents with stubs for the three required functions.
"""Your one-line package description."""
from __future__ import annotations
# import pandas as pd
__version__ = "0.1.0"
def function_one(arg1, arg2):
"""One-line summary."""
raise NotImplementedError
def function_two(arg1):
"""One-line summary."""
raise NotImplementedError
def function_three(arg1, arg2):
"""One-line summary."""
raise NotImplementedErrorReplace
function_one,function_two,function_threewith the actual function names from your brief, and use the parameter names from the brief (for example,load_route(origin, destination, place)forpedestrian-exposure).Commit and push.
git add .
git commit -m "scaffold package and stub public API"
git pushPart 4. Implement one function for real
You now pick one of the three functions and make it work end to end. Choose the simplest one in your brief. Examples by brief.
pedestrian-exposure, start withload_route. It just calls OSMnx and returns a GeoDataFrame.equitransport, start withload_nzdep. It joins one CSV.treecrown-nz, start withload_canopy. It calls the LINZ API for a small bbox.pavescore, start withscore_image. Use a single sample image and a placeholder texture-based score.escooter-akl, start withload_trips. It reads a small CSV and constructs Point geometries.
- Open the file where the function should live (often
__init__.pyfor a small package, or a dedicated module likeloader.py). Write the function. Add a NumPy-style docstring.
load_route for pedestrian-exposure
If your brief is pedestrian-exposure, a working first draft of load_route looks roughly like this. Copy it, run it, then adapt the addresses and the place name to whatever you need.
# src/pedestrian_exposure_uoa/loader.py
import osmnx as ox
def load_route(origin, destination, place="Auckland, New Zealand"):
"""Fetch the shortest walking route between two addresses.
Parameters
----------
origin : str
Starting address, e.g. "Britomart, Auckland".
destination : str
Ending address, e.g. "Karangahape Road, Auckland".
place : str, default "Auckland, New Zealand"
Place name used to scope the OSMnx graph download.
Returns
-------
geopandas.GeoDataFrame
Edges of the shortest walking route in EPSG:2193 (NZTM).
"""
# 1. Download the walking network for the area
G = ox.graph_from_place(place, network_type="walk")
# 2. Geocode the two addresses to (lat, lon) tuples
o_lat, o_lon = ox.geocoder.geocode(origin)
d_lat, d_lon = ox.geocoder.geocode(destination)
# 3. Snap each address to the nearest network node
orig_node = ox.distance.nearest_nodes(G, o_lon, o_lat)
dest_node = ox.distance.nearest_nodes(G, d_lon, d_lat)
# 4. Shortest path, then convert the node list to a GeoDataFrame of edges
nodes = ox.shortest_path(G, orig_node, dest_node, weight="length")
edges = ox.routing.route_to_gdf(G, nodes)
# 5. Reproject to NZTM so all spatial maths are in metres
return edges.to_crs("EPSG:2193")Try it in uv run python.
>>> from pedestrian_exposure_uoa import load_route
>>> edges = load_route("Britomart, Auckland", "Karangahape Road, Auckland")
>>> edges.shape
(42, 12) # something like this
>>> edges.crs
<Projected CRS: EPSG:2193>
>>> edges[["length", "highway"]].head()If the network download is slow, swap graph_from_place for graph_from_bbox with a small CBD bounding box. If geocoding fails for a casual address, pass an explicit (lat, lon) tuple instead and use nearest_nodes directly.
If your function loads data, decide now whether to bundle a small file inside the package or fetch from an API. Chapter 4.1, “Where does the data live?” walks through both patterns with code: importlib.resources for bundling, and the Stats NZ WFS pattern with caching for fetching. For most A3 briefs, you will use the fetch pattern.
- Try the function in an interactive session.
uv run python
>>> from your_package_name import function_one
>>> result = function_one(...)
>>> result.head()- Commit and push as soon as it works.
git commit -am "implement function_one with a working example"
git pushPart 5. Write the design report
The rest of the lab is the design report. This is the artefact you will refer back to in the Week 10 fit interview, when polishing the poster, and when writing the final README. Spend real time on it.
Create a Quarto file called design.qmd in the repo root. Using Quarto (rather than plain Markdown) means you can render it to HTML or PDF later for the poster and final report, and it gives you proper figure captions and cross-references for free.
Quarto starter template
Copy this into your design.qmd and fill in the placeholders.
---
title: "Design report: your-package-name"
authors:
- Partner A
- Partner B
date: today
format:
html:
toc: true
number-sections: true
crossref:
fig-title: "Figure"
fig-prefix: "Figure"
---
## Problem statement
Two sentences. What problem does the package solve, and for whom? Avoid jargon.
A council planner or another GISCI 343 student should understand it.
## Pipeline diagram
The package takes [input data] and turns it into [output deliverable].
@fig-pipeline shows the three functions and how data flows between them.
{#fig-pipeline width=80%}
## Expected output
@fig-output is the sketch of what the demo notebook will produce on the last cell.
{#fig-output width=80%}
If you cannot sketch what your output looks like, you do not yet know what
your package does.
## Key design choices
- **CRS**: we use EPSG:2193 (NZTM) because *(reason)*.
- **Data pattern**: data is *(bundled / fetched / both)* because *(reason)*.
- **Optional dependencies**: we keep *(library X)* as an optional extra because *(reason)*.
## Roles and next function
Partner A will own *(function name)* for the first draft. Partner B will own
*(function name)*. Both partners review all code.
Next function to implement after today's lab: *(function name)*.How figure captions and references work in Quarto
A figure with a caption and a label looks like this.
{#fig-pipeline width=80%}Three rules to remember.
- The text inside
![ ... ]becomes the caption that appears under the figure. - The
{#fig-name}is the label. Labels for figures must start withfig-. - Optional attributes go inside the same braces.
width=80%makes the figure take 80% of the page width. You can also writewidth=60%orwidth=4in.
To reference a figure elsewhere in the text, use @fig-name. Quarto turns this into “Figure 1”, “Figure 2”, and so on, automatically numbered.
@fig-pipeline shows the three functions and how data flows between them.Renders as:
Figure 1 shows the three functions and how data flows between them.
Where to save the images
Save your two PNGs in a docs/ folder inside the repo.
your-package-name/
├── design.qmd
├── docs/
│ ├── pipeline.png
│ └── expected_output.png
├── src/
└── pyproject.toml
Hand-drawn is fine for both images. Photograph them on your phone, crop, then save as PNG. If you prefer digital tools, Excalidraw and tldraw are good for quick architecture sketches.
Render to check
Before pushing, render the file once to confirm the captions and figures appear correctly.
quarto render design.qmdOpen the resulting design.html in a browser. Figures should be numbered (“Figure 1”, “Figure 2”) and the cross-references in the text should turn into clickable links.
You will refer back to design.qmd next week during the fit interview.
Part 6. Wrap up
Before the lab ends, confirm the following with the demonstrator.
- Your repo is on GitHub and both partners have push access.
uv buildruns without errors.- One function runs and returns something sensible in an interactive session.
design.qmdrenders without errors and shows both figures with numbered captions.
Further reading
- Chapter 4.1, Package fundamentals
akl-ped-countssource as a worked example, https://github.com/dataandcrowd/akl-ped-counts- uv quick start, https://docs.astral.sh/uv/getting-started/
- Excalidraw, https://excalidraw.com