Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.3.1 to 7.3.2. - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v7.3.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v7.3.2/packages/vite) --- updated-dependencies: - dependency-name: vite dependency-version: 7.3.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|---|---|---|
| backend | ||
| frontend | ||
| .air.toml | ||
| .env.example | ||
| .gitignore | ||
| compose.dev.yml | ||
| compose.test.yml | ||
| compose.yml | ||
| Dockerfile | ||
| Dockerfile.dev | ||
| LICENSE | ||
| README.md | ||
DMRmap
A web app that visualizes DMR repeaters on an interactive map. Filter by band and network, search by callsign or address, find repeaters along a driving route, and export CPS-ready channel configurations.
Live: https://dmrmap.kida.io
Features
- Interactive OpenStreetMap with repeater markers (blue = VHF/2m, red = UHF/70cm)
- Band filtering (2m / 70cm) and network filtering (BrandMeister, DMR+, TGIF, Other)
- Toggle visibility of personal hotspots and inactive repeaters
- Free-text search by callsign, city, or DMR ID
- Address-to-address routing via OSRM with adjustable corridor width
- Click-to-pin with adjustable radius search
- Detailed repeater popups (frequencies, color code, timeslots, hardware, power, antenna height, BrandMeister status)
- Real-time Last Heard heatmap via BrandMeister WebSocket
- CPS Studio: manage talk groups, set timeslots, export Motorola CPS 2.0 compatible XML
- Coordinate display with Maidenhead grid locator
- Dark mode (follows system preference)
- Internationalization (English, German, Spanish, French, Italian, Polish)
- No external CDN dependencies — all assets served locally
Tech Stack
- Backend: Go (stdlib
net/http), PostgreSQL 17 viapgx/v5, migrations viagoose/v3 - Frontend: TypeScript (bundled with esbuild), Leaflet, i18next
- Testing: Vitest (92 unit tests)
- Infrastructure: Docker Compose, multi-stage Dockerfile
External Services
| Service | Usage | Auth |
|---|---|---|
| RadioID | Repeater database (seeded from rptrs.json dump) |
None |
| BrandMeister API | Device status sync (/v2/device/{id}), talk group registry (/v2/talkgroup) |
None (public read) |
| BrandMeister WebSocket | Real-time Last Heard feed for heatmap via socket.io | None |
| Nominatim | Address geocoding and autocomplete (client-side) | None |
| OSRM | Driving route calculation (client-side) | None |
The BrandMeister talk group registry is fetched once at Docker build time and bundled as frontend/static/talkgroups.json. It is used for talk group name resolution and autocomplete in CPS Studio.
Running with Docker
docker compose up --build
Open http://localhost:8080. PostgreSQL data is persisted in a named volume.
Development
Hot-reload with air (Go) and esbuild watch (TypeScript):
docker compose -f compose.dev.yml up --build
The dev container mounts the project directory, so changes to Go and frontend files are picked up automatically.
Running Tests
docker compose -f compose.test.yml run --rm test
Environment Variables
| Variable | Default | Description |
|---|---|---|
DATABASE_URL |
postgres://dmrmap:dmrmap@localhost:5432/dmrmap?sslmode=disable |
PostgreSQL connection string |
JSON_PATH |
rptrs.json |
Path to RadioID repeater dump |
BMRPTRS_PATH |
bmrptrs.json |
Path to BrandMeister repeater dump |
LISTEN_ADDR |
:8080 |
HTTP listen address |
STATIC_DIR |
static |
Path to static frontend assets |
MIGRATIONS_DIR |
migrations |
Path to SQL migration files |
BM_SYNC |
(disabled) | Set to true to enable periodic BrandMeister device sync |
ADMIN_TOKEN |
(disabled) | Set to enable the admin interface at /admin/ |
API Endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/repeaters |
Repeaters within map bounding box (query: minLat, maxLat, minLng, maxLng, band, network, hotspots, inactive) |
| GET | /api/repeaters/radius |
Repeaters within radius of a point (query: lat, lng, radius) |
| POST | /api/repeaters/route |
Repeaters along a route corridor (body: GeoJSON coordinates + corridor width) |
| GET | /api/repeater |
Single repeater by ID (query: id) |
| GET | /api/repeaters/search |
Free-text search (query: q) |
Project Structure
├── backend/
│ ├── main.go # Route registration, env config
│ ├── db.go # DB queries, Repeater struct, geo helpers
│ ├── handlers.go # Public API handlers
│ ├── admin.go # Admin auth middleware + admin API
│ ├── bmdevice.go # BrandMeister device sync
│ ├── seed.go # Database seeding from JSON dumps
│ ├── migrate.go # Goose migration runner
│ ├── go.mod / go.sum
│ ├── migrations/ # SQL migrations (goose)
│ └── data/ # RadioID + BrandMeister JSON dumps
├── frontend/
│ ├── src/ # TypeScript source + tests
│ ├── static/
│ │ ├── index.html # Single-page HTML shell
│ │ ├── admin.html # Admin interface
│ │ ├── style.css # Styles with dark mode support
│ │ ├── i18n.js # i18next initialization
│ │ ├── locales/ # Translation files (en, de, es, fr, it, pl)
│ │ └── lib/ # Vendored JS libraries (Leaflet, i18next, socket.io)
│ ├── package.json
│ ├── tsconfig.json
│ └── vitest.config.ts
├── Dockerfile # Production multi-stage build
├── Dockerfile.dev # Development image with air + esbuild
├── compose.yml # Production Docker Compose
├── compose.dev.yml # Development Docker Compose
└── compose.test.yml # Test runner Docker Compose
Data
Repeater data provided by RadioID. Availability and talk group data by BrandMeister. This project is not affiliated with or endorsed by RadioID or BrandMeister.