MapToPoster / AGENTS.md
isaachwf's picture
Sync from IsaacHuo/maptoposter via hub-sync
4fb118c verified
|
Raw History Blame Contribute Delete
8.1 kB

AGENTS.md - City Map Poster Generator

This file provides essential information for agentic coding agents working in the maptoposter repository.

Project Overview

A Python-based web application that generates beautiful, minimalist map posters for any place in the world. The product UI is React/Vite over FastAPI, while OSMnx and Matplotlib power the map pipeline.

Tech Stack:

  • Python 3.12+
  • React + Vite (primary web UI)
  • FastAPI (application/API layer)
  • OSMnx (street network analysis)
  • Matplotlib (map rendering)
  • GeoPandas (geospatial data)
  • Nominatim (geocoding)

Build/Test/Lint Commands

Environment Setup

# Install dependencies with uv
uv sync --all-groups
corepack pnpm --dir frontend install --frozen-lockfile

Running the Application

# Start the API
uv run python app.py

# Start the frontend dev server in another terminal
corepack pnpm --dir frontend dev

# Or use the restart script (handles port conflicts)
bash restart.sh

# Development UI: http://localhost:5173
# Production-style UI/API: http://localhost:7860 after building frontend

Running Tests

# Run the full pytest suite
uv run pytest

# Run individual test functions
uv run pytest test_logic.py -k "china_hierarchy or manual_coordinates"

Development Utilities

# Verify city data integrity
uv run python verify_data.py

# Create map poster directly (CLI)
uv run python create_map_poster.py --city "Beijing" --country "China" --theme japanese_ink --output-format png

Code Style Guidelines

General Principles

  • Bilingual Support: All user-facing text should support both English (en) and Chinese (cn)
  • File Encoding: Use UTF-8 encoding with BOM for Chinese characters: # -*- coding: utf-8 -*-
  • Function Documentation: Use docstrings for all public functions
  • Error Handling: Provide meaningful error messages in both languages when possible

Import Organization

# Standard library imports first
import os
import json
import sys
from datetime import datetime

# Third-party imports next
from fastapi import FastAPI
import osmnx as ox
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
from geopy.geocoders import Nominatim

# Local imports last
from cities_data import get_countries, get_provinces, get_cities
from create_map_poster import generate_map_poster

Naming Conventions

  • Files: snake_case.py (e.g., cities_data.py, test_logic.py)
  • Variables/Functions: snake_case (e.g., get_available_themes(), motorway_color)
  • Constants: UPPER_SNAKE_CASE (e.g., THEMES_DIR, LAYER_KEYS)
  • Classes: PascalCase (rare in this codebase, but follow convention if used)

Type Hints

def generate_output_filename(city: str, theme_name: str, output_format: str) -> str:
    """Generate unique output filename with city, theme, and datetime."""
    pass

def get_theme_choices(lang: str = "en") -> list[tuple[str, str]]:
    """Return a list of (Display Name, Internal Name) tuples for themes."""
    pass

String Patterns

  • Language Keys: Use lang="en" or lang="cn" parameters
  • Layer Constants: Define parallel arrays for different languages
LAYERS_EN = ["Motorway", "Primary Roads", "Secondary Roads", "Water", "Parks"]
LAYERS_CN = ["ι«˜ι€Ÿε…¬θ·―", "主干道", "欑干道", "水域", "ε…¬ε›­"]
LAYER_KEYS = ["motorway", "primary", "secondary", "water", "parks"]

Data Structures

  • City Hierarchy: Use tuples for display/internal name pairs: [("Display Name", "internal_name"), ...]
  • Theme Files: JSON format with descriptive names: {"name": "Theme Name", "bg": "#FFFFFF", ...}
  • Coordinates: Use (lat, lon) tuple format

Directory Structure

maptoposter/
β”œβ”€β”€ app.py                 # FastAPI + built frontend entry point
β”œβ”€β”€ Dockerfile             # Hugging Face production container
β”œβ”€β”€ create_map_poster.py   # Core poster generation logic
β”œβ”€β”€ cities_data.py         # City/province/district data management
β”œβ”€β”€ test_logic.py          # Test suite for data logic
β”œβ”€β”€ verify_data.py         # Data integrity verification
β”œβ”€β”€ backend/               # FastAPI schemas and endpoints
β”œβ”€β”€ frontend/              # React/Vite editor
β”œβ”€β”€ docs/                  # Deployment and maintenance guides
β”œβ”€β”€ .github/workflows/     # Validation and Space publishing
β”œβ”€β”€ src/maptoposter/       # Typed core, cache, renderer, resources
β”œβ”€β”€ themes/                # JSON theme configuration files
β”œβ”€β”€ fonts/                 # Font files (Chinese and English)
β”œβ”€β”€ posters/               # Generated poster outputs
β”œβ”€β”€ public/                # Static assets and demo images
└── restart.sh            # Development restart script

Common Patterns

Language Support

def translate(text: str, lang: str = "en") -> str:
    """Simple translation wrapper."""
    translations = {
        "en": {"City": "City", "Theme": "Theme"},
        "cn": {"City": "εŸŽεΈ‚", "Theme": "主钘"}
    }
    return translations.get(lang, {}).get(text, text)

File Path Handling

THEMES_DIR = "themes"
FONTS_DIR = "fonts"
POSTERS_DIR = "posters"

# Always check existence
if not os.path.exists(THEMES_DIR):
    return []

FastAPI Endpoint Patterns

@app.get("/api/v1/health")
def health() -> dict[str, str]:
    """Return service readiness and version information."""
    return {"status": "ok", "version": __version__}

Testing Guidelines

Test Structure

  • Use pytest tests with focused fixtures and mocks
  • Group related tests in modules/functions (e.g., test_china_hierarchy())
  • Test both data integrity and coordinate retrieval
  • Include Chinese characters in test data

Test Execution

# Run all tests
uv run pytest

# The exact count grows with the maintained suite; require zero failures.

Error Handling

Common Patterns

# File not found handling
if not os.path.exists(font_path):
    print(f"⚠ Font not found: {font_path}")
    continue

# API error handling
try:
    result = geocoder.geocode(city)
except Exception as e:
    print(f"⚠ Geocoding failed for {city}: {e}")
    return None

# Data validation
assert len(provinces) > 30, f"Expected >30 provinces, found {len(provinces)}"

Performance Considerations

  • Caching: OSMnx caching is enabled: ox.settings.use_cache = True
  • Large Cities: Be aware that Beijing/Shanghai may have centering issues
  • Small Cities: Some layers may be empty due to missing OSM data
  • Memory: Clear matplotlib figures after generation to prevent memory leaks

Font Management

The project supports both English and Chinese fonts:

  • English: Goudy Old Style (Regular, Bold)
  • Chinese: HYWenRunSongYunU.ttf

Always verify font availability before use:

if os.path.exists(font_path):
    font = FontProperties(fname=font_path)
else:
    print(f"⚠ Font not found: {font_path}")
    font = None

Theme Development

Themes are JSON files in the themes/ directory with the following structure:

{
  "name": "Theme Display Name",
  "description": "Brief description",
  "bg": "#FFFFFF",
  "text": "#000000",
  "water": "#E0E0E0",
  "parks": "#F0F0F0",
  "road_motorway": "#FF0000",
  "road_primary": "#333333",
  "road_secondary": "#666666",
  "road_tertiary": "#999999",
  "road_residential": "#CCCCCC",
  "road_default": "#888888"
}

Development Notes

  • Port Management: Default port is 7860. Use restart.sh to handle conflicts
  • Output Directory: Generated posters go to posters/ (auto-created if needed)
  • Debugging: Use print statements with emoji indicators for important events
  • Code Organization: Keep API routes in backend/, UI code in frontend/, and domain logic in src/maptoposter/
  • Deployment: GitHub main is mirrored to the Docker Space only after CI validation
  • Dependencies: The project uses uv for dependency management. Lock file is uv.lock