map now working

This commit is contained in:
Davide Scaini
2026-03-28 19:34:22 +01:00
parent 5d58126d2f
commit 3441079913
18 changed files with 1489 additions and 10 deletions
+27
View File
@@ -25,6 +25,33 @@ def simplify_track(
return [p for (p, _, _), keep in zip(gps_pts, mask) if keep]
def preview_coords(
points: list[DataPoint],
max_points: int = 20,
) -> list[list[float]] | None:
"""Return a small list of [lat, lon] pairs for card thumbnail rendering.
Uses a coarser RDP pass, then subsamples to at most max_points.
Returns None if there is no GPS data.
"""
gps = [(p.lat, p.lon) for p in points if p.lat is not None and p.lon is not None]
if len(gps) < 2:
return None
# Coarse RDP (larger epsilon = fewer points)
coords = [[lon, lat] for lat, lon in gps]
mask = rdp(coords, epsilon=0.001, return_mask=True)
reduced = [gps[i] for i, keep in enumerate(mask) if keep]
# Subsample if still too many
if len(reduced) > max_points:
step = len(reduced) / max_points
reduced = [reduced[int(i * step)] for i in range(max_points)]
reduced.append(gps[-1]) # always include the last point
return [[round(lat, 5), round(lon, 5)] for lat, lon in reduced]
def build_geojson(
points: list[DataPoint],
activity_id: str,
+3 -1
View File
@@ -7,7 +7,7 @@ from pathlib import Path
from bincio.extract.metrics import ComputedMetrics
from bincio.extract.models import LapData, ParsedActivity
from bincio.extract.simplify import build_geojson
from bincio.extract.simplify import build_geojson, preview_coords
from bincio.extract.timeseries import build_timeseries
@@ -119,6 +119,8 @@ def build_summary(
"privacy": privacy,
"detail_url": f"activities/{activity_id}.json",
"track_url": f"activities/{activity_id}.geojson" if has_gps else None,
# Small track preview for card thumbnails — no separate fetch needed
"preview_coords": preview_coords(activity.points) if has_gps else None,
}
+141 -9
View File
@@ -1,4 +1,10 @@
"""bincio render — CLI command (stub, Astro stage TBD)."""
"""bincio render — build or serve the Astro static site."""
import os
import subprocess
import sys
from pathlib import Path
from typing import Optional
import click
from rich.console import Console
@@ -6,13 +12,139 @@ from rich.console import Console
console = Console()
def _find_site_dir(explicit: Optional[str]) -> Path:
"""Locate the Astro project directory."""
if explicit:
p = Path(explicit).expanduser().resolve()
if not (p / "package.json").exists():
raise click.UsageError(f"No package.json found in --site-dir {p}")
return p
# Search upward from cwd: ./site, ../site (for when cwd is bincio_data/)
for candidate in [Path.cwd() / "site", Path.cwd().parent / "site"]:
if (candidate / "package.json").exists():
return candidate
raise click.UsageError(
"Could not find the Astro site directory. "
"Run from the project root or pass --site-dir."
)
def _find_data_dir(explicit: Optional[str], config_path: Optional[str]) -> Path:
"""Resolve the BAS data directory."""
if explicit:
return Path(explicit).expanduser().resolve()
if config_path and Path(config_path).exists():
import yaml
raw = yaml.safe_load(Path(config_path).read_text())
out = raw.get("output", {}).get("dir")
if out:
return Path(out).expanduser().resolve()
# Default: ./bincio_data next to cwd
default = Path.cwd() / "bincio_data"
if default.exists():
return default
raise click.UsageError(
"Could not find the BAS data directory. "
"Run `bincio extract` first, or pass --data-dir."
)
def _ensure_npm(site: Path) -> None:
"""Run `npm install` if node_modules is missing or stale."""
if not (site / "node_modules").exists():
console.print("Running [cyan]npm install[/cyan]…")
subprocess.run(["npm", "install"], cwd=site, check=True)
def _link_data(site: Path, data: Path) -> None:
"""Symlink the BAS data store into site/public/data."""
public_data = site / "public" / "data"
if public_data.is_symlink():
if public_data.resolve() == data:
return # already correct
public_data.unlink()
elif public_data.exists():
console.print(
f"[yellow]Warning:[/yellow] {public_data} exists and is not a symlink — "
"remove it manually if you want bincio to manage it."
)
return
public_data.symlink_to(data)
console.print(f"Linked data: [cyan]{data}[/cyan] → [cyan]{public_data}[/cyan]")
@click.command()
@click.option("--config", "config_path", default="site_config.yaml")
@click.option("--out", "out_dir", default="./site/dist")
@click.option("--serve", is_flag=True, help="Start dev server with hot reload.")
@click.option("--config", "config_path", default=None,
help="Path to extract_config.yaml (reads output.dir from it).")
@click.option("--data-dir", default=None,
help="BAS data store directory (output of bincio extract).")
@click.option("--site-dir", default=None,
help="Astro project directory (default: ./site).")
@click.option("--out", "out_dir", default=None,
help="Build output directory (default: site/dist).")
@click.option("--serve", is_flag=True,
help="Start dev server with hot reload instead of building.")
@click.option("--deploy", default=None, metavar="TARGET",
help="Deploy target: 'github'.")
def render(config_path: str, out_dir: str, serve: bool, deploy: str | None) -> None:
"""Generate static site from BAS data store (Astro stage — coming soon)."""
console.print("[yellow]bincio render is not yet implemented.[/yellow]")
console.print("The web renderer (Astro + MapLibre + Observable Plot) is next.")
help="Deploy after build. Currently supports: github.")
def render(
config_path: Optional[str],
data_dir: Optional[str],
site_dir: Optional[str],
out_dir: Optional[str],
serve: bool,
deploy: Optional[str],
) -> None:
"""Build (or serve) the BincioActivity static site from a BAS data store."""
site = _find_site_dir(site_dir)
data = _find_data_dir(data_dir, config_path)
console.print(f"Site: [cyan]{site}[/cyan]")
console.print(f"Data: [cyan]{data}[/cyan]")
_ensure_npm(site)
_link_data(site, data)
env = {**os.environ, "BINCIO_DATA_DIR": str(data)}
if serve:
console.print("Starting [cyan]astro dev[/cyan]…")
subprocess.run(["npm", "run", "dev"], cwd=site, env=env)
return
# Build
cmd = ["npm", "run", "build"]
if out_dir:
# Pass outDir via Astro CLI flag
cmd = ["npx", "astro", "build", "--outDir", str(Path(out_dir).resolve())]
console.print("Running [cyan]astro build[/cyan]…")
result = subprocess.run(cmd, cwd=site, env=env)
if result.returncode != 0:
console.print("[red]Build failed.[/red]")
sys.exit(result.returncode)
dist = Path(out_dir).resolve() if out_dir else site / "dist"
console.print(f"\n[green]Build complete.[/green] Output: [cyan]{dist}[/cyan]")
if deploy == "github":
_deploy_github(site, dist)
def _deploy_github(site: Path, dist: Path) -> None:
"""Push dist/ to the gh-pages branch."""
console.print("Deploying to [cyan]GitHub Pages[/cyan]…")
# Requires npx gh-pages or git subtree push
result = subprocess.run(
["npx", "gh-pages", "-d", str(dist)],
cwd=site,
)
if result.returncode != 0:
console.print(
"[yellow]Tip:[/yellow] install gh-pages with `npm install -g gh-pages`"
)