How `get_perimeter`, `get_boundary`, and `parse_query` Work Together in prettymaps Fetch.py
The three helper functions in prettymaps/fetch.py—parse_query, get_boundary, and get_perimeter—transform any user query into a properly projected polygon that defines the map's geographic bounds.
parse_query identifies the input type. get_boundary creates radial shapes when a radius is specified. get_perimeter orchestrates the final perimeter, handling OSM lookups, aspect ratio scaling, and optional dilation. Together they form the foundation of Prettymaps' geographic query handling.
Overview of the Perimeter Pipeline
Every Prettymaps visualization starts with a perimeter—a polygon that defines what area to fetch from OpenStreetMap. In prettymaps/fetch.py, the three functions collaborate to normalize diverse inputs into a consistent output.
The pipeline handles four query types:
- GeoDataFrame – a pre-built polygon provided directly
- Coordinates – a
(longitude, latitude)tuple - OSM ID – a string like
"R146236"for relations,"W123"for ways - Address – any free-form place name
This design lets users specify locations flexibly while ensuring downstream code always receives a valid,EPSG:4326-projected polygon.
Step 1: parse_query Identifies the Input Type
parse_query (lines 87–97 in prettymaps/fetch.py) inspects the query and returns a discriminator string used by later functions.
from prettymaps.fetch import parse_query
parse_query("Central Park, NYC") # returns "address"
parse_query((-73.9654, 40.7829)) # returns "coordinates"
parse_query("R1204843") # returns "osmid"
parse_query(my_geodataframe) # returns "polygon"
The function uses simple type checking:
isinstance(query, tuple)→"coordinates"isinstance(query, str)starting withR,W, orN→"osmid"isinstance(query, str)→"address"- Has
.geometryattribute →"polygon"
This classification determines which path get_perimeter takes next.
Step 2: get_boundary Creates Radial Geometries
When a radius is provided with coordinates or an address, get_boundary (lines 99–128) generates the actual geometry. It produces either circular buffers or rotated square bounds.
The function follows this sequence:
- Geocode if needed – For addresses or OSM IDs, use
ox.geocoder.geocodeto resolve to a point - Project to UTM – Use
ox.projection.project_gdfto work in meters - Build shape – Circle via
buffer(radius)or square via custom polygon construction - Apply rotation – Optional rotation for square bounds
- Unproject to WGS84 – Return to EPSG:4326
from prettymaps.fetch import get_boundary
# Circle with 500m radius around coordinates
circle = get_boundary(
query=(-73.9855, 40.7580),
radius=500,
circle=True,
dilate=0
)
# Square rotated 45 degrees, 300m from center
square = get_boundary(
query="Times Square, NYC",
radius=300,
circle=False,
rotation=45,
dilate=0
)
The UTM projection is critical: buffering in degrees produces distorted shapes. By temporarily projecting to the appropriate UTM zone, the buffer distance in meters translates to accurate real-world dimensions.
Step 3: get_perimeter Orchestrates Final Output
get_perimeter (lines 132–173 in prettymaps/fetch.py) is the public interface. It decides whether to call get_boundary or handle the query directly, then applies final transformations.
Decision branch:
| Condition | Action |
|---|---|
radius provided |
Delegate to get_boundary |
| Query is GeoDataFrame | Return as-is |
| Otherwise | Fetch polygon via ox.geocoder.geocode_to_gdf |
Post-processing steps:
- Project to UTM for geometric operations
- If
aspect_ratiospecified, scale to maintain proportions - Re-project to EPSG:4326
- If
dilate> 0, apply final buffer expansion
from prettymaps.fetch import get_perimeter
# Fetch exact OSM boundary (no radius)
city = get_perimeter("Manhattan, New York", radius=None)
# 1km circle with 10% dilation
buffered = get_perimeter(
query="R2612945", # Manhattan relation ID
radius=1000,
circle=True,
dilate=100 # additional 100m buffer
)
# Custom aspect ratio for print dimensions
wide_map = get_perimeter(
query=(2.3522, 48.8566), # Paris
radius=800,
aspect_ratio=2.0 # 2:1 width:height
)
The aspect_ratio parameter is particularly useful for controlling the final map's proportions regardless of the geographic region's shape.
How the Functions Connect in Practice
The typical call chain in prettymaps.get_gdfs looks like:
# Simplified excerpt from prettymaps/fetch.py workflow
query = "R146236" # Central Park relation
radius = 500
# 1. Classify
query_type = parse_query(query) # → "osmid"
# 2. Build or fetch perimeter
perimeter = get_perimeter(
query=query,
radius=radius,
circle=True,
aspect_ratio=None,
dilate=0
)
# Internally calls get_boundary for radial generation
# 3. Use perimeter for all layer queries
# All OSM requests are clipped to this geometry
The perimeter becomes the spatial filter for every layer in the layers_dict passed to get_gdfs. This ensures building footprints, roads, water, and land use all align to the same geographic extent.
Complete Working Example
import prettymaps
from prettymaps.fetch import parse_query, get_boundary, get_perimeter
# --- Step by step exploration ---
# 1. Parse different query types
print(parse_query("Golden Gate Bridge")) # address
print(parse_query((-122.4783, 37.8199))) # coordinates
print(parse_query("W27164876")) # osmid (way)
# 2. Compare boundary shapes around same point
coords = (-122.4194, 37.7749) # SF City Hall
circle_boundary = get_boundary(coords, radius=400, circle=True)
square_boundary = get_boundary(coords, radius=400, circle=False, rotation=30)
print(f"Circle area: {circle_boundary.area:.6f} degrees²")
print(f"Square area: {square_boundary.area:.6f} degrees²")
# 3. Get final perimeters with different configurations
exact_sf = get_perimeter("San Francisco, CA", radius=None) # OSM boundary
radial_sf = get_perimeter(
"San Francisco, CA",
radius=2000,
circle=False,
aspect_ratio=1.5,
dilate=100
)
# 4. Use in full Prettymaps workflow
layers = {
"perimeter": {},
"streets": {"tags": {"highway": True}},
"buildings": {"tags": {"building": True}}
}
# get_gdfs calls get_perimeter internally for the perimeter layer
gdfs = prettymaps.get_gdfs("Mission District, SF", layers, radius=800)
Summary
-
parse_query(lines 87–97) classifies inputs as"polygon","coordinates","osmid", or"address"through simple type inspection. -
get_boundary(lines 99–128) creates circular or square/rotated geometries around points by projecting to UTM, buffering in meters, and returning to EPSG:4326. -
get_perimeter(lines 132–173) orchestrates the final output, choosing between boundary generation, direct polygon passthrough, or OSM polygon lookup, with optional aspect ratio scaling and dilation. -
Together in
prettymaps/fetch.py, these functions ensure any location query resolves to a consistent, properly projected perimeter that drives all subsequent OpenStreetMap data retrieval.
Frequently Asked Questions
What happens if I provide both a GeoDataFrame and a radius?
get_perimeter ignores the radius when the query is already a GeoDataFrame. The "polygon" type check takes precedence (line ~138), returning the supplied geometry unchanged. If you need to buffer an existing polygon, apply geopandas.GeoSeries.buffer() before passing it.
Why does get_boundary project to UTM instead of using EPSG:4326 directly?
Geometric operations like buffering require linear units (meters), but EPSG:4326 uses degrees. A degree of longitude varies from ~111km at the equator to 0 at the poles. By projecting to the appropriate UTM zone via ox.projection.project_gdf, the code ensures a 500-meter radius is actually 500 meters regardless of latitude.
How do aspect_ratio and dilate interact in get_perimeter?
Scaling to aspect_ratio happens before dilation. In the implementation (lines ~155–162), the geometry is first projected to UTM, then scaled to meet the requested width-to-height ratio, then re-projected. The dilate buffer (lines ~170–172) applies as a final uniform expansion in the UTM projection. Order matters: dilating first then scaling would create non-uniform buffer widths.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →