Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
version: 2
updates:
- package-ecosystem: npm
directory: /map
schedule:
interval: weekly
open-pull-requests-limit: 5
groups:
next:
patterns: ["next", "eslint-config-next"]
react:
patterns: ["react", "react-dom", "@types/react", "@types/react-dom"]
deck-gl:
patterns: ["@deck.gl/*"]
tailwind:
patterns: ["tailwindcss", "@tailwindcss/*", "tailwind-merge", "tw-animate-css"]
minor-and-patch:
update-types: ["minor", "patch"]

- package-ecosystem: npm
directory: /service
schedule:
interval: weekly
open-pull-requests-limit: 5
groups:
minor-and-patch:
update-types: ["minor", "patch"]

- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
groups:
actions:
patterns: ["*"]

- package-ecosystem: docker
directories: ["/map", "/service"]
schedule:
interval: weekly
100 changes: 100 additions & 0 deletions .github/workflows/deploy-map.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
name: Deploy Map (Cloudflare Pages)

# Builds the map as a static export and publishes it to Cloudflare Pages.
# - push to main -> production deployment
# - pull request (same repo) -> preview deployment
# - manual run -> deploys the selected ref
#
# Required repository secrets:
# CLOUDFLARE_API_TOKEN API token with "Account > Cloudflare Pages > Edit"
# CLOUDFLARE_ACCOUNT_ID Cloudflare account ID
# Optional repository variables:
# CLOUDFLARE_PAGES_PROJECT Pages project name (default: airframes-coverage)
# NEXT_PUBLIC_COVERAGE_API_URL Coverage API base URL
# NEXT_PUBLIC_MAP_STYLE_URL Default MapLibre style URL

on:
push:
branches: [main]
paths:
- map/**
- .github/workflows/deploy-map.yml
pull_request:
branches: [main]
paths:
- map/**
- .github/workflows/deploy-map.yml
workflow_dispatch:

concurrency:
group: deploy-map-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
deployments: write

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
# Fork and Dependabot pull requests do not receive secrets.
if: >-
github.event_name != 'pull_request' ||
(github.event.pull_request.head.repo.full_name == github.repository &&
github.actor != 'dependabot[bot]')
env:
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_COVERAGE_API_URL: ${{ vars.NEXT_PUBLIC_COVERAGE_API_URL || 'https://coverage-api.airframes.io' }}
NEXT_PUBLIC_WS_URL: ${{ vars.NEXT_PUBLIC_WS_URL || 'wss://coverage-api.airframes.io/ws' }}
NEXT_PUBLIC_MAP_STYLE_URL: ${{ vars.NEXT_PUBLIC_MAP_STYLE_URL || 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json' }}
PAGES_PROJECT: ${{ vars.CLOUDFLARE_PAGES_PROJECT || 'airframes-coverage' }}
CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }}
CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
steps:
- uses: actions/checkout@v5

- uses: actions/setup-node@v5
with:
node-version-file: map/.nvmrc
cache: npm
cache-dependency-path: map/package-lock.json

- name: Install
working-directory: map
run: npm ci

- name: Build static export
working-directory: map
run: npm run build:static

- name: Check Cloudflare credentials
id: creds
run: |
if [ -n "$CLOUDFLARE_API_TOKEN" ] && [ -n "$CLOUDFLARE_ACCOUNT_ID" ]; then
echo "present=true" >> "$GITHUB_OUTPUT"
else
echo "present=false" >> "$GITHUB_OUTPUT"
echo "::warning::CLOUDFLARE_API_TOKEN / CLOUDFLARE_ACCOUNT_ID are not set; skipping deploy."
fi

- name: Deploy to Cloudflare Pages
if: steps.creds.outputs.present == 'true'
id: deploy
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: map
command: >-
pages deploy out
--project-name=${{ env.PAGES_PROJECT }}
--branch=${{ github.head_ref || github.ref_name }}
--commit-hash=${{ github.event.pull_request.head.sha || github.sha }}
--commit-dirty=true

- name: Summary
if: steps.creds.outputs.present == 'true'
run: |
echo "### Cloudflare Pages deployment" >> "$GITHUB_STEP_SUMMARY"
echo "${{ steps.deploy.outputs.deployment-url }}" >> "$GITHUB_STEP_SUMMARY"
56 changes: 38 additions & 18 deletions .github/workflows/map.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,72 @@ name: Map CI
on:
push:
branches: [main]
paths: [map/**]
paths:
- map/**
- .github/workflows/map.yml
pull_request:
branches: [main]
paths: [map/**]
paths:
- map/**
- .github/workflows/map.yml

concurrency:
group: map-ci-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

defaults:
run:
working-directory: map

env:
NEXT_TELEMETRY_DISABLED: 1
NEXT_PUBLIC_COVERAGE_API_URL: https://coverage-api.airframes.io
NEXT_PUBLIC_WS_URL: wss://coverage-api.airframes.io/ws
NEXT_PUBLIC_MAP_STYLE_URL: https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json

jobs:
lint:
name: Lint
check:
name: Lint and typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
node-version-file: map/.nvmrc
cache: npm
cache-dependency-path: map/package-lock.json
- run: npm ci
- run: npm run lint
- run: npm run typecheck

build:
name: Build
runs-on: ubuntu-latest
needs: lint
env:
NEXT_PUBLIC_COVERAGE_API_URL: https://coverage-api.airframes.io
NEXT_PUBLIC_WS_URL: wss://coverage-api.airframes.io/ws
NEXT_PUBLIC_MAP_STYLE_URL: https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json
needs: check
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- uses: actions/checkout@v5
- uses: actions/setup-node@v5
with:
node-version: 22
node-version-file: map/.nvmrc
cache: npm
cache-dependency-path: map/package-lock.json
- run: npm ci
- run: npm run build
- name: Build (standalone server, used by Docker)
run: npm run build
- name: Build (static export, used by Cloudflare Pages)
run: npm run build:static
- name: Verify static export
run: test -f out/index.html

docker:
name: Docker Build
runs-on: ubuntu-latest
needs: build
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: docker/build-push-action@v6
with:
Expand All @@ -67,9 +86,10 @@ jobs:
needs: docker
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@v3
- uses: docker/login-action@v3
with:
Expand Down
31 changes: 30 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,33 @@ cd service && docker compose up
|----------|---------|-------------|
| `NEXT_PUBLIC_COVERAGE_API_URL` | `http://localhost:3002` | Coverage service API |
| `NEXT_PUBLIC_WS_URL` | `ws://localhost:3002/ws` | Coverage service WebSocket |
| `NEXT_PUBLIC_MAP_STYLE_URL` | CartoDB Dark Matter | MapLibre tile style URL |
| `NEXT_PUBLIC_MAP_STYLE_URL` | CartoDB Dark Matter | MapLibre style URL for the default (Dark) basemap |

## Deployment

### Map on Cloudflare Pages

The map is fully client-rendered, so it ships to Cloudflare Pages as a static export:

```bash
cd map
npm run build:static # writes map/out
npx wrangler pages deploy out --project-name=airframes-coverage
```

`.github/workflows/deploy-map.yml` does this automatically: pushes to `main` publish production,
pull requests publish a preview. It needs two repository secrets:

| Secret | Description |
|--------|-------------|
| `CLOUDFLARE_API_TOKEN` | API token with **Account → Cloudflare Pages → Edit** |
| `CLOUDFLARE_ACCOUNT_ID` | Cloudflare account ID |

Optional repository variables: `CLOUDFLARE_PAGES_PROJECT` (default `airframes-coverage`),
`NEXT_PUBLIC_COVERAGE_API_URL`, `NEXT_PUBLIC_WS_URL`, `NEXT_PUBLIC_MAP_STYLE_URL`.
The workflow skips the deploy step (with a warning) until the secrets exist.

### Docker

`npm run build` still produces the Next.js standalone server used by `map/Dockerfile`,
and CI publishes `ghcr.io/airframesio/coverage-map` and `ghcr.io/airframesio/coverage-service` from `main`.
Binary file added docs/screenshots/01-hexgrid.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/02-help-button.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/03-polygons.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/04-station-detail.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/05-marine-filter.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/06-3d-light-style.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/screenshots/07-fullscreen.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions map/.dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ node_modules
.env*.local
*.md
.git
out
1 change: 1 addition & 0 deletions map/.nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
9 changes: 7 additions & 2 deletions map/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ Airframes coverage map visualization built with Next.js, Deck.gl, MapLibre GL JS

## Commands
- `npm run dev` - Start dev server
- `npm run build` - Production build
- `npm run build` - Production build (standalone server, used by Docker)
- `npm run build:static` - Static export to `out/` (used by Cloudflare Pages)
- `npm run typecheck` - TypeScript check
- `npm run lint` - Lint

## Architecture
Expand All @@ -19,6 +21,9 @@ Airframes coverage map visualization built with Next.js, Deck.gl, MapLibre GL JS
- src/components/controls/ - UI controls (slider, toggle, legend)
- src/components/station/ - Station popup/detail
- src/lib/stores/ - Zustand stores
- src/lib/api/ - REST + WebSocket clients
- src/lib/constants/ - Config, colors, time windows
- src/lib/types/ - TypeScript types

## Notes
- maplibre-gl stays on 5.x: deck.gl 9.4's MapboxOverlay reads `map.transform`, which maplibre-gl 6 removed.
- Source-type colors live in `src/lib/constants/colors.ts` (`sourceColor`); do not add per-component palettes.
2 changes: 1 addition & 1 deletion map/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM node:22-alpine AS base
FROM node:24-alpine AS base

# --- Dependencies ---
FROM base AS deps
Expand Down
41 changes: 9 additions & 32 deletions map/README.md
Original file line number Diff line number Diff line change
@@ -1,36 +1,13 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).
# Airframes Coverage Map

## Getting Started

First, run the development server:
Next.js + deck.gl + MapLibre frontend for the Airframes coverage service. See the
[repository README](../README.md) for architecture, environment variables and deployment.

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
npm install
npm run dev # http://localhost:3000
npm run lint
npm run typecheck
npm run build # standalone server (Docker)
npm run build:static # static export in out/ (Cloudflare Pages)
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
13 changes: 12 additions & 1 deletion map/next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,18 @@
import type { NextConfig } from "next";

// The map is a fully client-rendered app, so it can ship two ways:
// - `standalone` (default): Node server used by the Docker image.
// - `export`: static files in `out/`, deployed to Cloudflare Pages.
// Selected with NEXT_OUTPUT=export (see `npm run build:static`).
const staticExport = process.env.NEXT_OUTPUT === "export";

const nextConfig: NextConfig = {
output: "standalone",
output: staticExport ? "export" : "standalone",
images: {
// Only a local SVG logo is rendered; no image optimizer is needed
// (and none exists on static hosting).
unoptimized: true,
},
};

export default nextConfig;
Loading
Loading