diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..9890697 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.py[cod] +*$py.class +*.so +.Python +build/ +dist/ +*.egg-info/ + +# IDE +.vscode +.claude + +# Environment +.venv +venv/ +.env + +# Documentation +README.md +CLAUDE.md + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# UV +.python-version +uv.lock diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2fb5e78 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,126 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +This is a collection of FastAPI-based tile servers that use rio-tiler to serve COG (Cloud Optimized GeoTIFF) files as dynamic map tiles. The project serves imagery from different planetary bodies: + +- **app.py** - NJ 2020 aerial imagery (I7D16.tif) - UInt16 data with per-band rescaling +- **app_world.py** - Natural Earth global basemap (HYP_HR_SR_OB_DR_cog.tif) - uint8 RGB +- **app_mars.py** - Mars MGS MOLA global basemap - Uses custom Mars CRS in equidistant cylindrical projection + +## Running the Servers + +Each app is an independent FastAPI application. Run with uvicorn: + +```bash +# Run NJ imagery server (default port 8000) +uvicorn app:app --reload + +# Run world basemap server +uvicorn app_world:app --port 8001 --reload + +# Run Mars basemap server +uvicorn app_mars:app --port 8002 --reload +``` + +## Architecture + +### Common Pattern (app.py, app_world.py) + +All apps follow the same pattern using `rio_tiler.io.Reader`: + +1. **Tile endpoint** (`/tiles/{z}/{x}/{y}.png`) - Reads COG tiles and returns PNG +2. **TileJSON endpoint** (`/tilejson.json`) - Returns TileJSON 2.2.0 metadata +3. **Optional viewer** (`/`) - app.py includes a Leaflet.js viewer + +Key rio-tiler concepts: +- `Reader(DATA_PATH)` context manager for reading COGs +- `cog.tile(x, y, z)` to extract tiles +- `img.render()` with `img_profiles` for output formatting +- `cog.get_geographic_bounds()` for bounds in geographic CRS +- `cog.minzoom`/`cog.maxzoom` for zoom levels + +### Mars CRS (app_mars.py) + +The Mars server uses a **custom TileMatrixSet** to handle Mars-specific coordinate system: + +- **CRS**: Simple Cylindrical Mars (equidistant cylindrical, meters) +- **Sphere radius**: 3,396,190 m (vs Earth's ~6,378,137 m) +- **Extent**: ±πR in x, ±πR/2 in y (calculated from Mars radius) +- **matrix_scale=[2, 1]**: 2×1 tiles at zoom 0, matching Cesium's GeographicTilingScheme + +**Critical design decision**: TMS and TIF both use the same Mars CRS (eqc meters). Avoid using longlat (degrees) because PROJ would attempt datum transformation through WGS84, causing "PROJ: eqc: Invalid latitude" errors due to the large sphere radius difference. + +```python +MARS_CRS = CRS.from_proj4( + "+proj=eqc +lat_ts=0 +lat_0=0 +lon_0=0 +x_0=0 +y_0=0 +a=3396190 +b=3396190 +units=m +no_defs" +) + +MARS_TMS = TileMatrixSet.custom( + crs=MARS_CRS, + extent=(-_MAX_X, -_MAX_Y, _MAX_X, _MAX_Y), + matrix_scale=[2, 1], +) +``` + +Pass custom TMS to Reader: `Reader(DATA_PATH, tms=MARS_TMS)` + +## Data Handling + +### Rescaling (app.py) + +For UInt16 imagery, use per-band rescaling based on p2/p98 statistics: + +```python +RESCALE = [ + (6130, 40453), # Red + (10641, 41211), # Green + (12482, 36323), # Blue +] + +img.render(img_format="PNG", rescale=RESCALE, **img_profiles.get("png", {})) +``` + +### uint8 Data (app_world.py, app_mars.py) + +No rescaling needed for pre-scaled uint8 imagery: + +```python +img.render(img_format="PNG", **img_profiles.get("png", {})) +``` + +## Dependencies + +Managed via uv with `pyproject.toml`: +- fastapi - Web framework +- rio-tiler - COG tiling engine +- rasterio - GeoTIFF I/O +- uvicorn - ASGI server +- pyproj, morecantile - CRS and TMS handling (for Mars app) + +## Adding New Tile Servers + +To add a new COG tile server: + +1. Copy the appropriate template (app.py for UInt16, app_world.py/app_mars.py for uint8) +2. Update `DATA_PATH` to point to your COG file in `data/` +3. For non-Earth data, create a custom CRS/TMS following app_mars.py pattern +4. Adjust rescaling if needed for UInt16 data +5. Update app metadata (title, description) +6. Run with uvicorn on a unique port + +## File Structure + +``` +data/ # COG files (gitignored) +├── I7D16.tif +├── HYP_HR_SR_OB_DR_cog.tif +└── Mars_MGS_MOLA_...tif + +app.py # NJ imagery server +app_world.py # World basemap server +app_mars.py # Mars basemap server +pyproject.toml # uv dependencies +``` diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..f339c8a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM python:3.12-slim + +WORKDIR /app + +RUN apt-get update && apt-get install -y \ + gcc \ + libssl-dev \ + libffi-dev \ + curl \ + libexpat1 \ + gdal-bin \ + libgdal-dev \ + && rm -rf /var/lib/apt/lists/* + +COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv + +COPY pyproject.toml ./ +COPY app_mars.py ./ + +RUN uv pip install --system -e . + +RUN mkdir -p /app/data + +EXPOSE 8002 + +CMD ["uv", "run", "uvicorn", "app_mars:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/README.md b/README.md index e29e857..c77afcb 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,9 @@ # rio-tiler-tms +## 手动构建镜像 + +sudo docker build -t rio-tiler-mars:latest . + +## 启动服务 + +sudo docker compose up -d \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6d735f4 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,15 @@ +services: + mars: + image: rio-tiler-mars:latest + container_name: rio-tiler-mars + ports: + - "8002:8000" + volumes: + - ./data:/app/data:ro + restart: unless-stopped + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:8002/tilejson.json"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 10s