Skip to content

Examples

Real-world examples of using RepeaterBook for common amateur radio tasks.

Example 1: Build a Repeater Directory Website

Create a web-based repeater directory with search functionality.

import asyncio
from flask import Flask, jsonify, request
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery, Status
from repeaterbook.queries import square, filter_radius, band, Bands
from repeaterbook.utils import LatLon, Radius
import pycountry

app = Flask(__name__)
rb = RepeaterBook(database="repeaters.db")

async def initialize_database():
    """Download and populate database on startup."""
    api = RepeaterBookAPI()

    # Download data for multiple countries
    countries = [
        pycountry.countries.get(alpha_2="US"),
        pycountry.countries.get(alpha_2="CA"),
        pycountry.countries.get(alpha_2="MX"),
    ]

    all_repeaters = []
    for country in countries:
        repeaters = await api.download(
            query=ExportQuery(countries={country})
        )
        all_repeaters.extend(repeaters)

    rb.populate(all_repeaters)
    print(f"Database initialized with {len(all_repeaters)} repeaters")

@app.route('/api/search')
def search_repeaters():
    """Search repeaters by location and filters."""
    # Get query parameters
    lat = float(request.args.get('lat'))
    lon = float(request.args.get('lon'))
    distance = float(request.args.get('distance', 50))
    mode = request.args.get('mode')  # 'dmr', 'p25', 'nxdn', 'analog'
    band_filter = request.args.get('band')  # '2m', '70cm', etc.

    # Build query
    radius = Radius(origin=LatLon(lat=lat, lon=lon), distance=distance)
    conditions = [
        square(radius),
        Repeater.operational_status == Status.ON_AIR
    ]

    # Add mode filter
    if mode == 'dmr':
        conditions.append(Repeater.dmr_capable == True)
    elif mode == 'p25':
        conditions.append(Repeater.apco_p_25_capable == True)
    elif mode == 'nxdn':
        conditions.append(Repeater.nxdn_capable == True)

    # Add band filter
    if band_filter == '2m':
        conditions.append(band(Bands.M_2))
    elif band_filter == '70cm':
        conditions.append(band(Bands.CM_70))

    # Execute query
    results = rb.query(*conditions)
    nearby = filter_radius(results, radius)

    # filter_radius returns repeaters sorted by distance
    # Limit to first 50 results
    sorted_results = nearby[:50]

    # Convert to JSON
    from haversine import haversine
    return jsonify([{
        'callsign': r.callsign,
        'frequency': r.frequency,
        'location': r.location_nearest_city,
        'distance': round(haversine(radius.origin, (r.latitude, r.longitude), unit=radius.unit), 2),
        'ctcss': r.pl_ctcss_uplink,
        'dmr': r.dmr_capable,
        'dmr_id': r.dmr_id,
        'dmr_cc': r.dmr_color_code,
    } for r in sorted_results])

if __name__ == '__main__':
    # Initialize database
    asyncio.run(initialize_database())

    # Start server
    app.run(debug=True)

Example 2: Generate Codeplug for DMR Radio

Create a CSV file for importing into a DMR radio codeplug.

import asyncio
import csv
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery, Status, Use
from repeaterbook.queries import square, filter_radius, band, Bands
from repeaterbook.utils import LatLon, Radius
import pycountry

async def generate_codeplug():
    """Generate DMR codeplug from RepeaterBook data."""

    # Download California repeaters
    api = RepeaterBookAPI()
    usa = pycountry.countries.get(alpha_2="US")
    repeaters = await api.download(
        query=ExportQuery(countries={usa}, state_ids={"06"})  # California FIPS code
    )

    # Store in database
    rb = RepeaterBook()
    rb.populate(repeaters)

    # Find DMR repeaters near San Francisco
    sf = LatLon(lat=37.7749, lon=-122.4194)
    radius = Radius(origin=sf, distance=100)

    dmr_repeaters = rb.query(
        square(radius),
        Repeater.dmr_capable == True,
        Repeater.operational_status == Status.ON_AIR,
        Repeater.use_membership == Use.OPEN,
        band(Bands.CM_70)  # 70cm only
    )

    nearby_dmr = filter_radius(dmr_repeaters, radius)
    # filter_radius returns repeaters sorted by distance

    # Generate CSV for Anytone/TYT radios
    with open('dmr_codeplug.csv', 'w', newline='') as f:
        writer = csv.writer(f)

        # Header row
        writer.writerow([
            'No.', 'Channel Name', 'Receive Frequency', 'Transmit Frequency',
            'Channel Type', 'Transmit Power', 'Band Width', 'CTCSS/DCS Decode',
            'CTCSS/DCS Encode', 'Contact', 'Contact Call Type', 'Radio ID',
            'Busy Lock/TX Permit', 'Squelch Mode', 'Optional Signal',
            'DTMF ID', 'Color Code', 'Slot', 'Scan List', 'Group List',
            'GPS System', 'Emergency System'
        ])

        # Data rows
        for idx, rep in enumerate(nearby_dmr[:100], start=1):
            # Calculate offset
            offset = rep.frequency - rep.input_frequency

            # Channel name
            name = f"{rep.callsign} {rep.location_nearest_city[:20]}"

            writer.writerow([
                idx,                          # No.
                name,                         # Channel Name
                f"{rep.frequency:.5f}",       # Receive Frequency
                f"{rep.input_frequency:.5f}", # Transmit Frequency
                'D-Digital',                  # Channel Type
                'High',                       # Transmit Power
                '12.5K',                      # Band Width
                f"{rep.pl_ctcss_uplink:.1f}" if rep.pl_ctcss_uplink else '',  # CTCSS Decode
                f"{rep.pl_ctcss_uplink:.1f}" if rep.pl_ctcss_uplink else '',  # CTCSS Encode
                'Worldwide',                  # Contact
                'Group Call',                 # Contact Call Type
                'None',                       # Radio ID
                'Always',                     # Busy Lock/TX Permit
                'Carrier',                    # Squelch Mode
                'Off',                        # Optional Signal
                '1',                          # DTMF ID
                rep.dmr_color_code or 1,      # Color Code
                '2',                          # Slot (usually TS2 for Worldwide)
                'All',                        # Scan List
                'Worldwide',                  # Group List
                'None',                       # GPS System
                'None'                        # Emergency System
            ])

    print(f"Generated codeplug with {len(nearby_dmr[:100])} channels")
    print(f"Import dmr_codeplug.csv into your radio programming software")

if __name__ == '__main__':
    asyncio.run(generate_codeplug())

Example 3: Repeater Coverage Map

Generate a heatmap of repeater coverage using folium.

import asyncio
import folium
from folium.plugins import HeatMap
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery, Status
import pycountry

async def create_coverage_map():
    """Create an interactive map of repeater locations."""

    # Download repeater data
    api = RepeaterBookAPI()
    uk = pycountry.countries.get(alpha_2="GB")
    repeaters = await api.download(query=ExportQuery(countries={uk}))

    # Store in database
    rb = RepeaterBook()
    rb.populate(repeaters)

    # Get operational repeaters
    operational = rb.query(Repeater.operational_status == Status.ON_AIR)

    # Create base map centered on UK
    m = folium.Map(
        location=[54.5, -4.0],
        zoom_start=6,
        tiles='OpenStreetMap'
    )

    # Add markers for each repeater
    for rep in operational:
        # Determine icon color by mode
        if rep.dmr_capable:
            color = 'blue'
            icon = 'info-sign'
        elif rep.apco_p_25_capable:
            color = 'green'
            icon = 'info-sign'
        elif rep.nxdn_capable:
            color = 'orange'
            icon = 'info-sign'
        else:
            color = 'red'
            icon = 'record'

        # Create popup with repeater info
        popup_html = f"""
        <div style="width:200px">
            <h4>{rep.callsign}</h4>
            <p><b>Frequency:</b> {rep.frequency:.4f} MHz</p>
            <p><b>Location:</b> {rep.location_nearest_city}</p>
            <p><b>Input Tone:</b> {rep.pl_ctcss_uplink or 'None'}</p>
            <p><b>Use:</b> {rep.use_membership.value}</p>
        </div>
        """

        folium.Marker(
            location=[rep.latitude, rep.longitude],
            popup=folium.Popup(popup_html, max_width=250),
            tooltip=rep.callsign,
            icon=folium.Icon(color=color, icon=icon)
        ).add_to(m)

    # Add heatmap layer
    heat_data = [[r.latitude, r.longitude] for r in operational]
    HeatMap(heat_data, radius=15).add_to(m)

    # Save map
    m.save('repeater_coverage.html')
    print("Map saved to repeater_coverage.html")

if __name__ == '__main__':
    asyncio.run(create_coverage_map())

Example 4: Repeater Statistics Dashboard

Analyze repeater data and generate statistics.

import asyncio
import pandas as pd
import matplotlib.pyplot as plt
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery
import pycountry

async def generate_statistics():
    """Generate statistics and visualizations from repeater data."""

    # Download data for multiple European countries
    api = RepeaterBookAPI()
    countries = [
        pycountry.countries.get(name="Germany"),
        pycountry.countries.get(name="France"),
        pycountry.countries.get(name="Italy"),
        pycountry.countries.get(name="Spain"),
        pycountry.countries.get(name="United Kingdom"),
    ]

    all_repeaters = []
    for country in countries:
        repeaters = await api.download(query=ExportQuery(countries={country}))
        all_repeaters.extend(repeaters)

    # Store in database
    rb = RepeaterBook()
    rb.populate(all_repeaters)

    # Query all repeaters
    all_data = rb.query()

    # Convert to DataFrame
    df = pd.DataFrame([r.model_dump() for r in all_data])

    # Statistics
    print("=== Repeater Statistics ===\n")

    print(f"Total Repeaters: {len(df)}")
    print(f"\nBy Status:")
    print(df['operational_status'].value_counts())

    print(f"\nBy Use/Membership:")
    print(df['use_membership'].value_counts())

    print(f"\nDigital Mode Capabilities:")
    print(f"DMR: {df['dmr_capable'].sum()}")
    print(f"P25: {df['apco_p_25_capable'].sum()}")
    print(f"NXDN: {df['nxdn_capable'].sum()}")
    print(f"Analog: {df['analog_capable'].sum()}")

    # Visualizations
    fig, axes = plt.subplots(2, 2, figsize=(12, 10))

    # 1. Frequency distribution
    df['frequency'].hist(bins=50, ax=axes[0, 0])
    axes[0, 0].set_title('Frequency Distribution')
    axes[0, 0].set_xlabel('Frequency (MHz)')
    axes[0, 0].set_ylabel('Count')

    # 2. Status pie chart
    df['operational_status'].value_counts().plot.pie(
        ax=axes[0, 1],
        autopct='%1.1f%%',
        title='Operational Status'
    )

    # 3. Digital modes bar chart
    modes = {
        'DMR': df['dmr_capable'].sum(),
        'P25': df['apco_p_25_capable'].sum(),
        'NXDN': df['nxdn_capable'].sum(),
        'Analog': df['analog_capable'].sum(),
    }
    pd.Series(modes).plot.bar(ax=axes[1, 0], title='Mode Capabilities')
    axes[1, 0].set_ylabel('Count')

    # 4. Access type distribution
    df['use_membership'].value_counts().plot.bar(
        ax=axes[1, 1],
        title='Access Type'
    )
    axes[1, 1].set_ylabel('Count')

    plt.tight_layout()
    plt.savefig('repeater_statistics.png', dpi=300)
    print("\nStatistics chart saved to repeater_statistics.png")

if __name__ == '__main__':
    asyncio.run(generate_statistics())

Example 5: Travel Planner

Find repeaters along a route for road trips.

import asyncio
from typing import List
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery, Status, Use
from repeaterbook.queries import filter_radius, square, band, Bands
from repeaterbook.utils import LatLon, Radius
import pycountry

class TravelPlanner:
    """Find repeaters along a travel route."""

    def __init__(self, rb: RepeaterBook):
        self.rb = rb

    def find_along_route(
        self,
        waypoints: List[LatLon],
        search_distance: float = 50,
        preferred_band: Bands = Bands.M_2,
        dmr_only: bool = False
    ) -> dict:
        """Find repeaters along a route."""

        results = {}

        for idx, waypoint in enumerate(waypoints):
            print(f"Searching around waypoint {idx + 1}...")

            # Build query
            radius = Radius(origin=waypoint, distance=search_distance)
            conditions = [
                square(radius),
                Repeater.operational_status == Status.ON_AIR,
                Repeater.use_membership == Use.OPEN,
                band(preferred_band)
            ]

            if dmr_only:
                conditions.append(Repeater.dmr_capable == True)

            # Execute query
            candidates = self.rb.query(*conditions)
            nearby = filter_radius(candidates, radius)

            # filter_radius returns repeaters sorted by distance
            # Take top 5 closest
            closest_repeaters = nearby[:5]

            results[f"Waypoint {idx + 1}"] = {
                'location': waypoint,
                'repeaters': closest_repeaters
            }

        return results

    def generate_report(self, results: dict) -> str:
        """Generate a text report of repeaters along route."""

        report = ["=" * 80]
        report.append("REPEATER TRAVEL PLAN")
        report.append("=" * 80)

        for waypoint_name, data in results.items():
            report.append(f"\n{waypoint_name}:")
            report.append(f"Location: {data['location']}")
            report.append(f"\nTop Repeaters:")

            for rep in data['repeaters']:
                # Calculate distance for display
                from haversine import haversine
                distance = haversine(data['location'], (rep.latitude, rep.longitude))
                report.append(f"\n  {rep.callsign} - {rep.frequency:.4f} MHz")
                report.append(f"  Location: {rep.location_nearest_city}")
                report.append(f"  Distance: {distance:.1f} km")
                if rep.pl_ctcss_uplink:
                    report.append(f"  Tone: {rep.pl_ctcss_uplink} Hz")
                if rep.dmr_capable:
                    report.append(f"  DMR: CC{rep.dmr_color_code}, ID {rep.dmr_id}")
                report.append(f"  Notes: {rep.notes or 'None'}")

            report.append("-" * 80)

        return "\n".join(report)

async def plan_road_trip():
    """Plan repeater coverage for a road trip."""

    # Download repeater data
    api = RepeaterBookAPI()
    usa = pycountry.countries.get(alpha_2="US")
    # FIPS codes: CA=06, NV=32, AZ=04, UT=49
    states = {"06", "32", "04", "49"}

    print("Downloading repeater data...")
    repeaters = await api.download(
        query=ExportQuery(countries={usa}, state_ids=states)
    )

    # Initialize database
    rb = RepeaterBook()
    rb.populate(repeaters)

    # Define route (San Francisco to Las Vegas to Phoenix)
    route = [
        LatLon(lat=37.7749, lon=-122.4194),  # San Francisco
        LatLon(lat=39.5296, lon=-119.8138),  # Reno
        LatLon(lat=36.1699, lon=-115.1398),  # Las Vegas
        LatLon(lat=33.4484, lon=-112.0740),  # Phoenix
    ]

    # Create planner
    planner = TravelPlanner(rb)

    # Find repeaters along route
    print("\nFinding repeaters along route...")
    results = planner.find_along_route(
        waypoints=route,
        search_distance=50,
        preferred_band=Bands.M_2,
        dmr_only=False
    )

    # Generate and save report
    report = planner.generate_report(results)
    print(report)

    with open('travel_plan.txt', 'w') as f:
        f.write(report)

    print("\nReport saved to travel_plan.txt")

if __name__ == '__main__':
    asyncio.run(plan_road_trip())

Example 6: Emergency Communications Planning

Identify repeaters with emergency capabilities.

import asyncio
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery, Status, Use
from repeaterbook.queries import square, filter_radius
from repeaterbook.utils import LatLon, Radius
import pycountry

async def emergency_planning():
    """Identify emergency communication resources."""

    # Download state data
    api = RepeaterBookAPI()
    usa = pycountry.countries.get(alpha_2="US")
    repeaters = await api.download(
        query=ExportQuery(countries={usa}, state_ids={"12"})  # Florida FIPS code
    )

    # Initialize database
    rb = RepeaterBook()
    rb.populate(repeaters)

    # Find emergency-capable repeaters (ARES/RACES/SKYWARN)
    emergency_repeaters = rb.query(
        Repeater.operational_status == Status.ON_AIR,
        (Repeater.ares == True) | (Repeater.races == True) | (Repeater.skywarn == True),
        Repeater.use_membership.in_([Use.OPEN, Use.PRIVATE])
    )

    print(f"Found {len(emergency_repeaters)} emergency-capable repeaters\n")

    # Group by county/location
    by_location = {}
    for rep in emergency_repeaters:
        location = rep.location_nearest_city
        if location not in by_location:
            by_location[location] = []
        by_location[location].append(rep)

    # Generate report
    print("=" * 80)
    print("EMERGENCY COMMUNICATIONS RESOURCES")
    print("=" * 80)

    for location, reps in sorted(by_location.items()):
        print(f"\n{location}:")
        for rep in sorted(reps, key=lambda r: r.frequency):
            print(f"  {rep.callsign:10s} {rep.frequency:8.4f} MHz", end="")
            if rep.pl_ctcss_uplink:
                print(f"  Tone: {rep.pl_ctcss_uplink:6.1f}", end="")
            print(f"  Use: {rep.use_membership.value}")
            if rep.notes:
                print(f"    Notes: {rep.notes}")

    # Find repeaters near major cities
    print("\n" + "=" * 80)
    print("COVERAGE NEAR MAJOR CITIES")
    print("=" * 80)

    cities = {
        "Miami": LatLon(lat=25.7617, lon=-80.1918),
        "Tampa": LatLon(lat=27.9506, lon=-82.4572),
        "Orlando": LatLon(lat=28.5383, lon=-81.3792),
        "Jacksonville": LatLon(lat=30.3322, lon=-81.6557),
    }

    for city_name, city_loc in cities.items():
        radius = Radius(origin=city_loc, distance=25)
        candidates = rb.query(
            square(radius),
            Repeater.operational_status == Status.ON_AIR,
            (Repeater.ares == True) | (Repeater.races == True) | (Repeater.skywarn == True)
        )
        nearby = filter_radius(candidates, radius)

        print(f"\n{city_name}: {len(nearby)} emergency repeaters within 25km")

if __name__ == '__main__':
    asyncio.run(emergency_planning())

Example 7: DMR Repeater Analysis

Analyze DMR repeater distribution and color codes.

import asyncio
from collections import Counter
from repeaterbook import RepeaterBook, Repeater
from repeaterbook.services import RepeaterBookAPI
from repeaterbook.models import ExportQuery, Status
import pycountry

async def analyze_dmr():
    """Analyze DMR repeater distribution."""

    # Download data
    api = RepeaterBookAPI()
    usa = pycountry.countries.get(alpha_2="US")
    repeaters = await api.download(query=ExportQuery(countries={usa}))

    rb = RepeaterBook()
    rb.populate(repeaters)

    # Get DMR repeaters
    dmr_repeaters = rb.query(
        Repeater.dmr_capable == True,
        Repeater.operational_status == Status.ON_AIR
    )

    print("=" * 80)
    print("DMR REPEATER ANALYSIS")
    print("=" * 80)
    print(f"\nTotal Operational DMR Repeaters: {len(dmr_repeaters)}")

    # Count by state
    states = Counter(r.state for r in dmr_repeaters if r.state)
    print(f"\nTop 10 States by DMR Repeaters:")
    for state, count in states.most_common(10):
        percentage = (count / len(dmr_repeaters)) * 100
        print(f"  {state:25s} {count:5d} ({percentage:5.1f}%)")

    # Find most popular color codes
    color_codes = Counter(r.dmr_color_code for r in dmr_repeaters if r.dmr_color_code)

    print(f"\nColor Code Distribution:")
    for cc, count in sorted(color_codes.items()):
        percentage = (count / len(dmr_repeaters)) * 100
        bar = "█" * int(percentage)
        print(f"  CC{cc}: {bar} {count:4d} ({percentage:5.1f}%)")

    # Count repeaters with DMR IDs
    with_dmr_id = sum(1 for r in dmr_repeaters if r.dmr_id)
    print(f"\nRepeaters with DMR ID: {with_dmr_id} ({with_dmr_id/len(dmr_repeaters)*100:.1f}%)")

if __name__ == '__main__':
    asyncio.run(analyze_dmr())

Next Steps