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.

  1. A package skeleton on disk and on GitHub.
  2. A pyproject.toml that builds without errors.
  3. A first working function with a docstring.
  4. 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).
  • uv installed and working.
  • A shared GitHub repository for your pair (you will create it during the lab).
Pair work, in plain language

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

  1. Open your assigned brief in 4.5.
  2. Underline the three required functions. Write the signatures on a sheet of paper or in a shared document.
  3. Underline the demonstration dataset. Note the source URL.
  4. Identify one part of the brief you are unsure about and ask the demonstrator before moving on.

Part 2. Create the package skeleton together

  1. One partner creates a new GitHub repository for your pair (private or public, your choice). Add the other partner as a collaborator.

  2. 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.

  1. Open pyproject.toml. Update the name, description, and authors fields. Both partners go in authors.
[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" },
]
  1. Build to confirm everything is wired correctly.
uv build

If 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

  1. 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 NotImplementedError
  1. Replace function_one, function_two, function_three with the actual function names from your brief, and use the parameter names from the brief (for example, load_route(origin, destination, place) for pedestrian-exposure).

  2. Commit and push.

git add .
git commit -m "scaffold package and stub public API"
git push

Part 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 with load_route. It just calls OSMnx and returns a GeoDataFrame.
  • equitransport, start with load_nzdep. It joins one CSV.
  • treecrown-nz, start with load_canopy. It calls the LINZ API for a small bbox.
  • pavescore, start with score_image. Use a single sample image and a placeholder texture-based score.
  • escooter-akl, start with load_trips. It reads a small CSV and constructs Point geometries.
  1. Open the file where the function should live (often __init__.py for a small package, or a dedicated module like loader.py). Write the function. Add a NumPy-style docstring.
Worked example: 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.

Tip

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.

  1. Try the function in an interactive session.
uv run python
>>> from your_package_name import function_one
>>> result = function_one(...)
>>> result.head()
  1. Commit and push as soon as it works.
git commit -am "implement function_one with a working example"
git push

Part 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.

![Pipeline from input data through the three functions to the final deliverable.](docs/pipeline.png){#fig-pipeline width=80%}

## Expected output

@fig-output is the sketch of what the demo notebook will produce on the last cell.

![Sketch of the expected demo output (rough map of the route coloured by exposure).](docs/expected_output.png){#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.

![Caption text goes here.](docs/pipeline.png){#fig-pipeline width=80%}

Three rules to remember.

  1. The text inside ![ ... ] becomes the caption that appears under the figure.
  2. The {#fig-name} is the label. Labels for figures must start with fig-.
  3. Optional attributes go inside the same braces. width=80% makes the figure take 80% of the page width. You can also write width=60% or width=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.qmd

Open 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 build runs without errors.
  • One function runs and returns something sensible in an interactive session.
  • design.qmd renders without errors and shows both figures with numbered captions.

Further reading

  • Chapter 4.1, Package fundamentals
  • akl-ped-counts source 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