first commit
This commit is contained in:
384
.gitignore
vendored
384
.gitignore
vendored
@@ -1,190 +1,194 @@
|
|||||||
# ---> Python
|
# custom gitignore for python projects
|
||||||
# Byte-compiled / optimized / DLL files
|
.claude/
|
||||||
__pycache__/
|
.vscode/
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
# ---> Python
|
||||||
|
# Byte-compiled / optimized / DLL files
|
||||||
# C extensions
|
__pycache__/
|
||||||
*.so
|
*.py[cod]
|
||||||
|
*$py.class
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
# C extensions
|
||||||
build/
|
*.so
|
||||||
develop-eggs/
|
|
||||||
dist/
|
# Distribution / packaging
|
||||||
downloads/
|
.Python
|
||||||
eggs/
|
build/
|
||||||
.eggs/
|
develop-eggs/
|
||||||
lib/
|
dist/
|
||||||
lib64/
|
downloads/
|
||||||
parts/
|
eggs/
|
||||||
sdist/
|
.eggs/
|
||||||
var/
|
lib/
|
||||||
wheels/
|
lib64/
|
||||||
share/python-wheels/
|
parts/
|
||||||
*.egg-info/
|
sdist/
|
||||||
.installed.cfg
|
var/
|
||||||
*.egg
|
wheels/
|
||||||
MANIFEST
|
share/python-wheels/
|
||||||
|
*.egg-info/
|
||||||
# PyInstaller
|
.installed.cfg
|
||||||
# Usually these files are written by a python script from a template
|
*.egg
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
MANIFEST
|
||||||
*.manifest
|
|
||||||
*.spec
|
# PyInstaller
|
||||||
|
# Usually these files are written by a python script from a template
|
||||||
# Installer logs
|
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||||
pip-log.txt
|
*.manifest
|
||||||
pip-delete-this-directory.txt
|
*.spec
|
||||||
|
|
||||||
# Unit test / coverage reports
|
# Installer logs
|
||||||
htmlcov/
|
pip-log.txt
|
||||||
.tox/
|
pip-delete-this-directory.txt
|
||||||
.nox/
|
|
||||||
.coverage
|
# Unit test / coverage reports
|
||||||
.coverage.*
|
htmlcov/
|
||||||
.cache
|
.tox/
|
||||||
nosetests.xml
|
.nox/
|
||||||
coverage.xml
|
.coverage
|
||||||
*.cover
|
.coverage.*
|
||||||
*.py,cover
|
.cache
|
||||||
.hypothesis/
|
nosetests.xml
|
||||||
.pytest_cache/
|
coverage.xml
|
||||||
cover/
|
*.cover
|
||||||
|
*.py,cover
|
||||||
# Translations
|
.hypothesis/
|
||||||
*.mo
|
.pytest_cache/
|
||||||
*.pot
|
cover/
|
||||||
|
|
||||||
# Django stuff:
|
# Translations
|
||||||
*.log
|
*.mo
|
||||||
local_settings.py
|
*.pot
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
# Django stuff:
|
||||||
|
*.log
|
||||||
# Flask stuff:
|
local_settings.py
|
||||||
instance/
|
db.sqlite3
|
||||||
.webassets-cache
|
db.sqlite3-journal
|
||||||
|
|
||||||
# Scrapy stuff:
|
# Flask stuff:
|
||||||
.scrapy
|
instance/
|
||||||
|
.webassets-cache
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
# Scrapy stuff:
|
||||||
|
.scrapy
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
# Sphinx documentation
|
||||||
target/
|
docs/_build/
|
||||||
|
|
||||||
# Jupyter Notebook
|
# PyBuilder
|
||||||
.ipynb_checkpoints
|
.pybuilder/
|
||||||
|
target/
|
||||||
# IPython
|
|
||||||
profile_default/
|
# Jupyter Notebook
|
||||||
ipython_config.py
|
.ipynb_checkpoints
|
||||||
|
|
||||||
# pyenv
|
# IPython
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
profile_default/
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
ipython_config.py
|
||||||
# .python-version
|
|
||||||
|
# pyenv
|
||||||
# pipenv
|
# For a library or package, you might want to ignore these files since the code is
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
# intended to run in multiple environments; otherwise, check them in:
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
# .python-version
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
# pipenv
|
||||||
#Pipfile.lock
|
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||||
|
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||||
# UV
|
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
# install all needed dependencies.
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
#Pipfile.lock
|
||||||
# commonly ignored for libraries.
|
|
||||||
#uv.lock
|
# UV
|
||||||
|
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
|
||||||
# poetry
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
# commonly ignored for libraries.
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
#uv.lock
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
# poetry
|
||||||
#poetry.lock
|
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||||
|
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||||
# pdm
|
# commonly ignored for libraries.
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||||
#pdm.lock
|
#poetry.lock
|
||||||
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
||||||
# in version control.
|
# pdm
|
||||||
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
||||||
.pdm.toml
|
#pdm.lock
|
||||||
.pdm-python
|
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
||||||
.pdm-build/
|
# in version control.
|
||||||
|
# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
.pdm.toml
|
||||||
__pypackages__/
|
.pdm-python
|
||||||
|
.pdm-build/
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
||||||
celerybeat.pid
|
__pypackages__/
|
||||||
|
|
||||||
# SageMath parsed files
|
# Celery stuff
|
||||||
*.sage.py
|
celerybeat-schedule
|
||||||
|
celerybeat.pid
|
||||||
# Environments
|
|
||||||
.env
|
# SageMath parsed files
|
||||||
.venv
|
*.sage.py
|
||||||
env/
|
|
||||||
venv/
|
# Environments
|
||||||
ENV/
|
.env
|
||||||
env.bak/
|
.venv
|
||||||
venv.bak/
|
env/
|
||||||
|
venv/
|
||||||
# Spyder project settings
|
ENV/
|
||||||
.spyderproject
|
env.bak/
|
||||||
.spyproject
|
venv.bak/
|
||||||
|
|
||||||
# Rope project settings
|
# Spyder project settings
|
||||||
.ropeproject
|
.spyderproject
|
||||||
|
.spyproject
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
# Rope project settings
|
||||||
|
.ropeproject
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
# mkdocs documentation
|
||||||
.dmypy.json
|
/site
|
||||||
dmypy.json
|
|
||||||
|
# mypy
|
||||||
# Pyre type checker
|
.mypy_cache/
|
||||||
.pyre/
|
.dmypy.json
|
||||||
|
dmypy.json
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
# Pyre type checker
|
||||||
|
.pyre/
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
# pytype static type analyzer
|
||||||
|
.pytype/
|
||||||
# PyCharm
|
|
||||||
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
# Cython debug symbols
|
||||||
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
cython_debug/
|
||||||
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
||||||
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
# PyCharm
|
||||||
#.idea/
|
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
||||||
|
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
||||||
# Ruff stuff:
|
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
||||||
.ruff_cache/
|
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
||||||
|
#.idea/
|
||||||
# PyPI configuration file
|
|
||||||
.pypirc
|
# Ruff stuff:
|
||||||
|
.ruff_cache/
|
||||||
# ---> VisualStudioCode
|
|
||||||
.vscode/*
|
# PyPI configuration file
|
||||||
!.vscode/settings.json
|
.pypirc
|
||||||
!.vscode/tasks.json
|
|
||||||
!.vscode/launch.json
|
# ---> VisualStudioCode
|
||||||
!.vscode/extensions.json
|
.vscode/*
|
||||||
!.vscode/*.code-snippets
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
# Local History for Visual Studio Code
|
!.vscode/launch.json
|
||||||
.history/
|
!.vscode/extensions.json
|
||||||
|
!.vscode/*.code-snippets
|
||||||
# Built Visual Studio Code Extensions
|
|
||||||
*.vsix
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Built Visual Studio Code Extensions
|
||||||
|
*.vsix
|
||||||
|
|
||||||
|
|||||||
127
CLAUDE.md
Normal file
127
CLAUDE.md
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
||||||
|
|
||||||
|
## Project Overview
|
||||||
|
|
||||||
|
Mars Titiler is a FastAPI-based tile server for Mars MGS MOLA Color-Shaded Relief global basemap data. It uses the Titiler framework to serve Cloud Optimized GeoTIFFs (COG) with a custom Mars coordinate system.
|
||||||
|
|
||||||
|
## Environment Setup
|
||||||
|
|
||||||
|
The project uses a conda environment named `gdal_env`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
source /home/la/miniconda3/bin/activate gdal_env
|
||||||
|
```
|
||||||
|
|
||||||
|
This environment includes:
|
||||||
|
- FastAPI + Uvicorn (web server)
|
||||||
|
- rio-tiler (COG reading)
|
||||||
|
- morecantile (tile matrix sets)
|
||||||
|
- pyproj (coordinate reference systems)
|
||||||
|
- GDAL dependencies
|
||||||
|
|
||||||
|
## Running the Server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Direct uvicorn
|
||||||
|
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
|
||||||
|
# Or use the provided script
|
||||||
|
./start_server.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
The server will be available at `http://localhost:8000` with API docs at `/docs`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
Run the comprehensive test suite:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
python test_all.py [base_url] [data_path]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example:
|
||||||
|
```bash
|
||||||
|
# Default (localhost:8000, default data path)
|
||||||
|
python test_all.py
|
||||||
|
|
||||||
|
# Custom server and data
|
||||||
|
python test_all.py http://localhost:8000 /path/to/data.tif
|
||||||
|
```
|
||||||
|
|
||||||
|
The test script validates:
|
||||||
|
- Data info endpoint
|
||||||
|
- TileJSON metadata
|
||||||
|
- Tile generation
|
||||||
|
- Preview images
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Custom Mars Coordinate System
|
||||||
|
|
||||||
|
The core of this project is the Mars Simple Cylindrical projection defined in `app.py`:
|
||||||
|
|
||||||
|
- **Projection**: Equidistant Cylindrical (Simple Cylindrical)
|
||||||
|
- **Sphere Radius**: 3,396,190 meters
|
||||||
|
- **Extent**:
|
||||||
|
- X: ±10,669,442 m (±180° longitude)
|
||||||
|
- Y: ±5,334,721 m (±90° latitude)
|
||||||
|
- **TMS Configuration**: `matrix_scale=[2, 1]` creates a 2×1 tile layout at zoom 0
|
||||||
|
|
||||||
|
The `matrix_scale=[2, 1]` is critical for compatibility with Cesium's `GeographicTilingScheme`, ensuring zoom 0 has exactly 2 tiles horizontally and 1 tile vertically.
|
||||||
|
|
||||||
|
### Titiler Factory Pattern
|
||||||
|
|
||||||
|
The application uses Titiler's `TilerFactory` to automatically generate endpoints:
|
||||||
|
|
||||||
|
```python
|
||||||
|
cog = TilerFactory(
|
||||||
|
reader=Reader,
|
||||||
|
supported_tms=tms,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
This factory pattern eliminates the need to manually implement endpoints. All Titiler endpoints are automatically available under the `/cog` path prefix.
|
||||||
|
|
||||||
|
### Custom TMS Registration
|
||||||
|
|
||||||
|
The custom Mars TMS is registered with morecantile before being passed to the factory:
|
||||||
|
|
||||||
|
```python
|
||||||
|
MARS_TMS = TileMatrixSet.custom(
|
||||||
|
crs=MARS_CRS,
|
||||||
|
extent=(-_MAX_X, -_MAX_Y, _MAX_X, _MAX_Y),
|
||||||
|
identifier="MarsCylindrical",
|
||||||
|
matrix_scale=[2, 1],
|
||||||
|
)
|
||||||
|
tms = tms.register({"MarsCylindrical": MARS_TMS})
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
All endpoints require a `url` query parameter specifying the COG file path:
|
||||||
|
|
||||||
|
- **Tiles**: `/cog/tiles/MarsCylindrical/{z}/{x}/{y}?url=<path>`
|
||||||
|
- **TileJSON**: `/cog/MarsCylindrical/tilejson.json?url=<path>`
|
||||||
|
- **Info**: `/cog/info?url=<path>` (bands, bounds, CRS metadata)
|
||||||
|
- **Preview**: `/cog/preview.png?url=<path>&max_size=512`
|
||||||
|
- **Statistics**: `/cog/statistics?url=<path>`
|
||||||
|
- **Map Viewer**: `/cog/MarsCylindrical/map.html?url=<path>`
|
||||||
|
|
||||||
|
## Data Source
|
||||||
|
|
||||||
|
The default data file is expected at:
|
||||||
|
```
|
||||||
|
/home/la/studio/cesium/rio-tiler-tms/data/Mars_MGS_MOLA_ClrShade_merge_global_463m.tif
|
||||||
|
```
|
||||||
|
|
||||||
|
This is a Cloud Optimized GeoTIFF containing Mars MGS MOLA Color-Shaded Relief data.
|
||||||
|
|
||||||
|
## Design Principles
|
||||||
|
|
||||||
|
1. **Dynamic URLs**: Data paths are passed as query parameters, not hardcoded. This allows the same server to serve different datasets.
|
||||||
|
|
||||||
|
2. **Standards Compliance**: The TMS follows OGC standards and is compatible with Cesium's GeographicTilingScheme.
|
||||||
|
|
||||||
|
3. **Minimal Code**: Titiler's factory pattern auto-generates endpoints, resulting in ~70% less code compared to manual implementations.
|
||||||
95
README.md
95
README.md
@@ -1,2 +1,95 @@
|
|||||||
# mars-titiler
|
# Mars Titiler Server
|
||||||
|
|
||||||
|
基于 Titiler 的火星瓦片服务,用于提供 Mars MGS MOLA Color-Shaded Relief 全球底图。
|
||||||
|
|
||||||
|
## 功能特点
|
||||||
|
|
||||||
|
- 使用 Titiler 框架,代码简洁高效
|
||||||
|
- 自定义火星坐标系(SimpleCylindrical,米制)
|
||||||
|
- 支持 Cesium GeographicTilingScheme (2×1 瓦片布局)
|
||||||
|
- 自动提供多个端点:tiles、tilejson、info、preview、statistics 等
|
||||||
|
|
||||||
|
## 快速启动
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 激活环境
|
||||||
|
source /home/la/miniconda3/bin/activate gdal_env
|
||||||
|
|
||||||
|
# 启动服务
|
||||||
|
uvicorn app:app --reload --host 0.0.0.0 --port 8000
|
||||||
|
```
|
||||||
|
|
||||||
|
## API 端点
|
||||||
|
|
||||||
|
所有端点都需要 `url` 参数指定数据文件路径。
|
||||||
|
|
||||||
|
### 核心端点
|
||||||
|
|
||||||
|
- **瓦片**: `/cog/tiles/MarsCylindrical/{z}/{x}/{y}?url=<path>`
|
||||||
|
- 格式:PNG、WebP、JPEG 等(默认 PNG)
|
||||||
|
|
||||||
|
- **TileJSON**: `/cog/MarsCylindrical/tilejson.json?url=<path>`
|
||||||
|
- 返回 TileJSON 2.2.0 元数据
|
||||||
|
|
||||||
|
- **数据信息**: `/cog/info?url=<path>`
|
||||||
|
- 返回栅格数据的详细信息(波段、范围、CRS 等)
|
||||||
|
|
||||||
|
- **预览**: `/cog/preview.png?url=<path>&max_size=512`
|
||||||
|
- 返回全局预览图
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 设置数据路径
|
||||||
|
DATA_PATH="/home/la/studio/cesium/rio-tiler-tms/data/Mars_MGS_MOLA_ClrShade_merge_global_463m.tif"
|
||||||
|
|
||||||
|
# 获取数据信息
|
||||||
|
curl "http://localhost:8000/cog/info?url=$DATA_PATH"
|
||||||
|
|
||||||
|
# 获取 TileJSON
|
||||||
|
curl "http://localhost:8000/cog/MarsCylindrical/tilejson.json?url=$DATA_PATH"
|
||||||
|
|
||||||
|
# 获取瓦片 (zoom=0, x=0, y=0)
|
||||||
|
curl "http://localhost:8000/cog/tiles/MarsCylindrical/0/0/0?url=$DATA_PATH" -o tile.png
|
||||||
|
|
||||||
|
# 获取预览图
|
||||||
|
curl "http://localhost:8000/cog/preview.png?url=$DATA_PATH&max_size=512" -o preview.png
|
||||||
|
```
|
||||||
|
|
||||||
|
## 与原版对比
|
||||||
|
|
||||||
|
### 原版 (rio-tiler-tms)
|
||||||
|
```
|
||||||
|
/tiles/{z}/{x}/{y}.png
|
||||||
|
/tilejson.json
|
||||||
|
```
|
||||||
|
- 数据路径硬编码
|
||||||
|
- 手动实现端点
|
||||||
|
|
||||||
|
### Titiler 版本
|
||||||
|
```
|
||||||
|
/cog/tiles/MarsCylindrical/{z}/{x}/{y}?url=<path>
|
||||||
|
/cog/MarsCylindrical/tilejson.json?url=<path>
|
||||||
|
/cog/info?url=<path>
|
||||||
|
/cog/preview.png?url=<path>
|
||||||
|
/cog/statistics?url=<path>
|
||||||
|
...更多端点
|
||||||
|
```
|
||||||
|
- 数据路径通过参数传递,更灵活
|
||||||
|
- 自动生成所有端点
|
||||||
|
- 代码量减少 70%
|
||||||
|
|
||||||
|
## 技术细节
|
||||||
|
|
||||||
|
### 火星坐标系
|
||||||
|
|
||||||
|
- **投影**: 等距柱状投影 (Equidistant Cylindrical)
|
||||||
|
- **球体半径**: 3,396,190 米
|
||||||
|
- **范围**:
|
||||||
|
- X: ±10,669,442 米 (±180°)
|
||||||
|
- Y: ±5,334,721 米 (±90°)
|
||||||
|
- **TMS**: matrix_scale=[2, 1],zoom 0 有 2×1 个瓦片
|
||||||
|
|
||||||
|
### 与 Cesium 集成
|
||||||
|
|
||||||
|
该 TMS 设计与 Cesium 的 GeographicTilingScheme 完全兼容,无需修改客户端代码。
|
||||||
|
|||||||
63
app.py
Normal file
63
app.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""Titiler-based Mars tile server for MGS MOLA ClrShade global basemap"""
|
||||||
|
|
||||||
|
import math
|
||||||
|
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from pyproj import CRS
|
||||||
|
from morecantile import tms, TileMatrixSet
|
||||||
|
from rio_tiler.io import Reader
|
||||||
|
|
||||||
|
from titiler.core.factory import TilerFactory
|
||||||
|
|
||||||
|
# Mars SimpleCylindrical CRS (equidistant cylindrical projection in meters)
|
||||||
|
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 extent in equidistant cylindrical projection
|
||||||
|
# x = R × lon_rad, y = R × lat_rad
|
||||||
|
_R = 3396190
|
||||||
|
_MAX_X = _R * math.pi # ≈ 10,669,442 m (corresponds to ±180°)
|
||||||
|
_MAX_Y = _R * math.pi / 2 # ≈ 5,334,721 m (corresponds to ±90°)
|
||||||
|
|
||||||
|
# Custom TMS for Mars with matrix_scale=[2, 1] to match Cesium GeographicTilingScheme
|
||||||
|
MARS_TMS = TileMatrixSet.custom(
|
||||||
|
crs=MARS_CRS,
|
||||||
|
extent=(-_MAX_X, -_MAX_Y, _MAX_X, _MAX_Y),
|
||||||
|
identifier="MarsCylindrical",
|
||||||
|
matrix_scale=[2, 1],
|
||||||
|
)
|
||||||
|
|
||||||
|
# Register custom TMS
|
||||||
|
tms = tms.register({"MarsCylindrical": MARS_TMS})
|
||||||
|
|
||||||
|
# Create FastAPI app
|
||||||
|
app = FastAPI(
|
||||||
|
title="Titiler Mars",
|
||||||
|
description="Mars MGS MOLA Color-Shaded Relief global tile server powered by Titiler",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create COG Tiler with Mars TMS support
|
||||||
|
cog = TilerFactory(
|
||||||
|
reader=Reader,
|
||||||
|
supported_tms=tms,
|
||||||
|
)
|
||||||
|
|
||||||
|
app.include_router(cog.router, tags=["Cloud Optimized GeoTIFF"])
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/", include_in_schema=False)
|
||||||
|
def landing():
|
||||||
|
"""Landing page."""
|
||||||
|
return {
|
||||||
|
"title": "Titiler Mars Tile Server",
|
||||||
|
"description": "Mars MGS MOLA Color-Shaded Relief global tile server",
|
||||||
|
"endpoints": {
|
||||||
|
"tiles": "/tiles/MarsCylindrical/{z}/{x}/{y}?url=<data_url>",
|
||||||
|
"tilejson": "/MarsCylindrical/tilejson.json?url=<data_url>",
|
||||||
|
"info": "/info?url=<data_url>",
|
||||||
|
"preview": "/preview.png?url=<data_url>&max_size=512",
|
||||||
|
"docs": "/docs",
|
||||||
|
},
|
||||||
|
}
|
||||||
50
docker-compose.yml
Normal file
50
docker-compose.yml
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
services:
|
||||||
|
mars-titiler:
|
||||||
|
image: ghcr.io/developmentseed/titiler:latest
|
||||||
|
|
||||||
|
ports:
|
||||||
|
- "8001:8000"
|
||||||
|
|
||||||
|
# 启动自定义的 Mars Titiler 应用
|
||||||
|
command: [
|
||||||
|
"uvicorn",
|
||||||
|
"app:app",
|
||||||
|
"--host", "0.0.0.0",
|
||||||
|
"--port", "8000",
|
||||||
|
"--reload"
|
||||||
|
]
|
||||||
|
|
||||||
|
working_dir: /mars-titiler
|
||||||
|
|
||||||
|
environment:
|
||||||
|
# Python 模块路径
|
||||||
|
- PYTHONPATH=/mars-titiler
|
||||||
|
# GDAL 性能优化配置
|
||||||
|
- CPL_TMPDIR=/tmp
|
||||||
|
- GDAL_CACHEMAX=75%
|
||||||
|
- GDAL_INGESTED_BYTES_AT_OPEN=32768
|
||||||
|
- GDAL_DISABLE_READDIR_ON_OPEN=EMPTY_DIR
|
||||||
|
- GDAL_HTTP_MERGE_CONSECUTIVE_RANGES=YES
|
||||||
|
- GDAL_HTTP_MULTIPLEX=YES
|
||||||
|
- GDAL_HTTP_VERSION=2
|
||||||
|
- VSI_CACHE=TRUE
|
||||||
|
- VSI_CACHE_SIZE=536870912
|
||||||
|
- PYTHONWARNINGS=ignore
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
# 挂载项目目录到容器
|
||||||
|
- .:/mars-titiler
|
||||||
|
# 挂载数据目录
|
||||||
|
- /home/la/studio/cesium/rio-tiler-tms/data:/data:ro
|
||||||
|
|
||||||
|
# 可选:如果使用 gunicorn 生产环境,取消注释以下配置
|
||||||
|
# command: [
|
||||||
|
# "gunicorn",
|
||||||
|
# "-k", "uvicorn.workers.UvicornWorker",
|
||||||
|
# "app:app",
|
||||||
|
# "--bind", "0.0.0.0:8000",
|
||||||
|
# "--workers", "2",
|
||||||
|
# "--timeout", "120"
|
||||||
|
# ]
|
||||||
|
|
||||||
|
restart: unless-stopped
|
||||||
123
test_all.py
Normal file
123
test_all.py
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""完整测试 Titiler Mars 瓦片服务的所有功能"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# 默认数据路径
|
||||||
|
DEFAULT_DATA = (
|
||||||
|
Path(__file__).parent.parent
|
||||||
|
/ "rio-tiler-tms"
|
||||||
|
/ "data"
|
||||||
|
/ "Mars_MGS_MOLA_ClrShade_merge_global_463m.tif"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_server(base_url: str, data_path: str):
|
||||||
|
"""测试服务器的各个端点"""
|
||||||
|
|
||||||
|
print(f"🔍 测试 Mars Titiler 服务器")
|
||||||
|
print(f" 服务地址: {base_url}")
|
||||||
|
print(f" 数据文件: {data_path}\n")
|
||||||
|
print("=" * 70)
|
||||||
|
|
||||||
|
tests = [
|
||||||
|
{
|
||||||
|
"name": "1. 获取数据信息 (/info)",
|
||||||
|
"url": f"{base_url}/info?url={data_path}",
|
||||||
|
"show_keys": ["width", "height", "count", "bounds", "minzoom", "maxzoom"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "2. 获取 TileJSON (/MarsCylindrical/tilejson.json)",
|
||||||
|
"url": f"{base_url}/MarsCylindrical/tilejson.json?url={data_path}",
|
||||||
|
"show_keys": ["tilejson", "minzoom", "maxzoom", "bounds", "tiles"],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "3. 获取瓦片 (/tiles/MarsCylindrical/0/0/0.png)",
|
||||||
|
"url": f"{base_url}/tiles/MarsCylindrical/0/0/0.png?url={data_path}",
|
||||||
|
"binary": True,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "4. 获取预览图 (/preview.png)",
|
||||||
|
"url": f"{base_url}/preview.png?url={data_path}&max_size=256",
|
||||||
|
"binary": True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
all_passed = True
|
||||||
|
|
||||||
|
for test in tests:
|
||||||
|
print(f"\n{test['name']}")
|
||||||
|
print(f" URL: {test['url']}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(test["url"], timeout=10)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f" ✓ 状态: {response.status_code} OK")
|
||||||
|
|
||||||
|
if test.get("binary"):
|
||||||
|
print(f" ✓ 大小: {len(response.content):,} bytes")
|
||||||
|
print(f" ✓ 类型: {response.headers.get('content-type')}")
|
||||||
|
else:
|
||||||
|
data = response.json()
|
||||||
|
if "show_keys" in test:
|
||||||
|
for key in test["show_keys"]:
|
||||||
|
if key in data:
|
||||||
|
value = data[key]
|
||||||
|
if isinstance(value, list) and len(str(value)) > 60:
|
||||||
|
print(f" - {key}: [列表长度 {len(value)}]")
|
||||||
|
elif isinstance(value, str) and len(value) > 60:
|
||||||
|
print(f" - {key}: {value[:60]}...")
|
||||||
|
else:
|
||||||
|
print(f" - {key}: {value}")
|
||||||
|
else:
|
||||||
|
print(f" ✗ 失败: HTTP {response.status_code}")
|
||||||
|
print(f" 错误: {response.text[:200]}")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f" ✗ 异常: {e}")
|
||||||
|
all_passed = False
|
||||||
|
|
||||||
|
print("\n" + "=" * 70)
|
||||||
|
if all_passed:
|
||||||
|
print("✓ 所有测试通过!")
|
||||||
|
print("\n🎉 Mars Titiler 瓦片服务运行正常!")
|
||||||
|
else:
|
||||||
|
print("✗ 部分测试失败")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
print("\n📖 其他可用端点:")
|
||||||
|
print(f" - API 文档: {base_url}/docs")
|
||||||
|
print(f" - 地图查看器: {base_url}/MarsCylindrical/map.html?url={data_path}")
|
||||||
|
print(f" - 统计信息: {base_url}/statistics?url={data_path}")
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
base_url = sys.argv[1]
|
||||||
|
else:
|
||||||
|
base_url = "http://localhost:8001"
|
||||||
|
|
||||||
|
if len(sys.argv) > 2:
|
||||||
|
data_path = sys.argv[2]
|
||||||
|
else:
|
||||||
|
data_path = str(DEFAULT_DATA)
|
||||||
|
|
||||||
|
if not Path(data_path).exists():
|
||||||
|
print(f"❌ 错误: 数据文件不存在: {data_path}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# 测试服务器是否在运行
|
||||||
|
try:
|
||||||
|
requests.get(f"{base_url}/", timeout=2)
|
||||||
|
except requests.exceptions.RequestException:
|
||||||
|
print(f"❌ 错误: 无法连接到服务器 {base_url}")
|
||||||
|
print("请先启动服务器: ./start_server.sh 或 uvicorn app:app")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
sys.exit(test_server(base_url, data_path))
|
||||||
Reference in New Issue
Block a user