Interface web cartographique: COG + TiTiler + viewer MapLibre

- Ajout de convert_to_cog() et generate_cog_metadata() dans rendering.py
- Nouveau module viewer.py: génération HTML MapLibre GL JS avec couches et opacité
- Nouveau module server.py: serveur FastAPI avec TiTiler pour tuiles COG
- Pipeline: étapes 5 (COGs) et 6 (viewer web) après le rapport PDF
- CLI: flag --no-viewer pour désactiver la génération du viewer
- run.sh: commande 'serve' pour démarrer le serveur sur port 8000
- Dockerfile: ajout de rio-cogeo, titiler.core, fastapi, uvicorn, piexif
- setup.py: point d'entrée lidar-server

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-05-10 17:15:37 +02:00
parent 2986400a0a
commit f01683819c
9 changed files with 779 additions and 21 deletions

117
lidar_pipeline/server.py Normal file
View File

@ -0,0 +1,117 @@
"""TiTiler-based web server for serving LiDAR COG visualizations.
Provides:
- COG tile serving via TiTiler API
- Static file serving for viewer HTML
- CORS headers for local development
Usage:
python -m lidar_pipeline.server /path/to/output [--port 8000]
"""
import logging
import sys
from pathlib import Path
logger = logging.getLogger("lidar")
def create_app(output_dir):
"""Create the FastAPI application with TiTiler and static file serving.
Args:
output_dir: Path to the output directory containing visualisations/ and viewer/.
Returns:
FastAPI application instance.
"""
from fastapi import FastAPI
from fastapi.responses import HTMLResponse, FileResponse, JSONResponse
from fastapi.middleware.cors import CORSMiddleware
from titiler.core.factory import TilerFactory
output_dir = Path(output_dir).resolve()
# TiTiler COG endpoint
cog = TilerFactory(router_prefix="/cog")
app = FastAPI(title="LiDAR Archéologique", docs_url="/docs")
# CORS for local development
app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"])
# Mount TiTiler routes
app.include_router(cog.router, prefix="/cog")
@app.get("/")
async def index():
"""Landing page with links."""
return HTMLResponse(
'<html><head><title>LiDAR Server</title>'
'<style>body{font-family:sans-serif;max-width:600px;margin:40px auto;padding:0 20px}'
'h1{color:#1a1a2e}a{color:#4fc3f7}</style></head>'
'<body>'
'<h1>Serveur LiDAR Archéologique</h1>'
'<p><a href="/viewer">Visualisations</a></p>'
'<p>TiTiler API: <a href="/cog/">/cog/</a></p>'
'</body></html>'
)
@app.get("/viewer/{basename}")
@app.get("/viewer")
async def serve_viewer(basename: str = ""):
"""Serve viewer HTML files."""
if not basename:
# List available viewers
viewer_dir = output_dir / 'visualisations' / 'viewer'
if viewer_dir.exists():
viewers = sorted(viewer_dir.glob('*.html'))
if viewers:
links = ''.join(
f'<li><a href="/viewer/{f.stem}">{f.stem}</a></li>'
for f in viewers
)
return HTMLResponse(
f'<html><head><title>LiDAR Viewer</title>'
f'<style>body{{font-family:sans-serif;max-width:600px;margin:40px auto;padding:0 20px}}'
f'h1{{color:#1a1a2e}}li{{margin:8px 0}}a{{color:#4fc3f7}}</style></head>'
f'<body><h1>Visualisations LiDAR</h1><ul>{links}</ul></body></html>'
)
return HTMLResponse('<html><body><p>Aucun viewer disponible</p></body></html>')
viewer_file = output_dir / 'visualisations' / 'viewer' / f"{basename}.html"
if viewer_file.exists():
return FileResponse(str(viewer_file), media_type='text/html')
return JSONResponse({'error': f'Viewer not found: {basename}'}, status_code=404)
return app
def main():
"""Entry point for the LiDAR web server."""
import argparse
import uvicorn
parser = argparse.ArgumentParser(description='Serveur cartographique LiDAR')
parser.add_argument('output_dir', help='Répertoire de sortie contenant les visualisations')
parser.add_argument('--host', default='0.0.0.0', help='Hôte (défaut: 0.0.0.0)')
parser.add_argument('--port', type=int, default=8000, help='Port (défaut: 8000)')
args = parser.parse_args()
output_dir = Path(args.output_dir)
if not output_dir.exists():
logger.error(f"Répertoire introuvable: {output_dir}")
sys.exit(1)
app = create_app(output_dir)
print(f"Serveur LiDAR Archéologique")
print(f" Viewer: http://{args.host}:{args.port}/viewer")
print(f" TiTiler: http://{args.host}:{args.port}/cog/")
print(f" Répertoire: {output_dir}")
uvicorn.run(app, host=args.host, port=args.port, log_level='info')
if __name__ == '__main__':
main()