diff --git a/content/radio/local-repeaters.md b/content/radio/local-repeaters.md
new file mode 100644
index 0000000..e9fe0d5
--- /dev/null
+++ b/content/radio/local-repeaters.md
@@ -0,0 +1,47 @@
+---
+title: Local Repeaters
+extra:
+ css_import:
+ - https://unpkg.com/leaflet@1.9.4/dist/leaflet.css
+ head_js_import:
+ - https://unpkg.com/leaflet@1.9.4/dist/leaflet.js
+ js_import:
+ - /js/radio/local-repeaters.js
+---
+
+This page keeps track of local repeaters. See incorrect data here? Please [contact me](/contact).
+
+## Analog Repeaters
+
+
+
+
+
+| Callsign | Downlink | Uplink | PL | Coverage | Reach | Links |
+|----------|----------|---------|-------|-----------------------------------------------------------|--------|----------------|
+| VA3ATL | 444.775 | 449.775 | 110.9 | GTA | 70 km | |
+| VE3WOO | 443.900 | 448.900 | 127.3 | GTA | 50 km | |
+| VE3WOO | 442.750 | 447.750 | 103.5 | GTA | | VE3WOO |
+| VE3WOO | 444.175 | 449.175 | 103.5 | GTA | 50 km | VE3WOO |
+| VE3ZOE | 443.075 | 448.075 | | Golden Horseshoe | 100 km | |
+| VE3GRW | 442.900 | 447.900 | 107.2 | Golden Horseshoe | 200 km | |
+| VE3YYZ | 443.050 | 448.050 | 156.7 | Toronto | 35 km | VE3RTR |
+| VE3OKR | 442.450 | 447.450 | | Oakville | 40 km | |
+| VE3RSB | 444.825 | 449.825 | 131.8 | Burlington | 40 km | |
+| VE3ADT | 444.125 | 449.125 | 131.8 | Milton | 45 km | |
+| VE3MIS | 145.430 | 144.830 | 103.5 | Mississauga | 20 km | |
+| VE3YRA | 145.350 | 144.750 | 103.5 | GTA | 75 km | |
+| VE3SKY | 146.985 | 146.385 | 103.5 | GTA | 70 km | |
+| VE3GYQ | 145.350 | 144.750 | 114.8 | London | 50 km | |
+| VE3TTT | 147.180 | 147.780 | 114.8 | London | 75 km | VE3SUE |
+| VE3SUE | 444.400 | 449.400 | 114.8 | London | 50 km | |
+| VA3FEZ | 444.100 | 449.100 | 114.8 | London | | VE3MMX |
+| VE3MMX | 147.225 | 147.825 | 114.8 | St Thomas | 35 km | |
+| VE3MMX | 443.750 | 448.750 | 114.8 | St Thomas | | |
+| VE3TCB | 146.940 | 146.340 | 114.8 | Grand Bend | 50 km | VE3SUE, VE3SRT |
+| VE3SRT | 442.050 | 447.050 | 114.8 | Grand Bend | 50 km | VE3SUE |
+| VE3MCR | 147.000 | 147.600 | 114.8 | Grand Bend | | VE3SUE |
+| VE3RGB | 146.760 | 146.160 | 114.8 | Grand Bend | | VE3SUE |
+| VE3RTR | 444.975 | 449.975 | 162.2 | Peterborough | 65 km | |
+
+
diff --git a/static/dist/icons8/antenna.png b/static/dist/icons8/antenna.png
new file mode 100644
index 0000000..9d8fa9d
Binary files /dev/null and b/static/dist/icons8/antenna.png differ
diff --git a/static/js/radio/local-repeaters.js b/static/js/radio/local-repeaters.js
new file mode 100644
index 0000000..baa1ef8
--- /dev/null
+++ b/static/js/radio/local-repeaters.js
@@ -0,0 +1,83 @@
+// Configure the maps (center on Toronto)
+var analog_rpt_map = L.map('analog-repeater-map').setView([43.6532, -79.3832], 8);
+
+// Add OSM base map
+L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
+ maxZoom: 18
+}).addTo(analog_rpt_map);
+
+// Process repeater tables
+var analog_repeaters = Array.from(document.querySelectorAll("#analog-repeaters tbody tr")).map(function (row) {
+ console.log(row);
+ return {
+ callsign: row.cells[0].innerText,
+ downlink: row.cells[1].innerText,
+ uplink: row.cells[2].innerText,
+ pl_tone: row.cells[3].innerText,
+ grid: row.cells[4].innerText,
+ latitude: row.cells[4].querySelector("span").attributes["lat"].value,
+ longitude: row.cells[4].querySelector("span").attributes["lon"].value,
+ reach: row.cells[5].innerText.split(" ")[0],
+ linked_repeaters: row.cells[6].innerText.split(", ")
+ };
+});
+
+// Add repeater markers
+analog_repeaters.forEach(function (repeater) {
+ console.log(repeater);
+ var marker = L.marker([repeater.latitude, repeater.longitude]).addTo(analog_rpt_map);
+ marker.bindPopup(`${repeater.callsign}`);
+ // marker.setIcon(L.divIcon({
+ // className: 'repeater-marker',
+ // iconSize: [16, 16]
+ // }));
+});
+
+// Draw the coverage areas
+analog_repeaters.forEach(function (repeater) {
+ if (repeater.reach != "") {
+ var coverage = L.circle([repeater.latitude, repeater.longitude], {
+ color: 'gray',
+ opacity: 0.25,
+ fillColor: 'gray',
+ fillOpacity: 0.05,
+ radius: repeater.reach * 1000
+ }).addTo(analog_rpt_map);
+ coverage.bindPopup(`${repeater.callsign} coverage`);
+ }
+});
+
+// Draw the repeater links
+analog_repeaters.forEach(function (repeater) {
+ repeater.linked_repeaters.forEach(function (linked_repeater) {
+ // Find that repeater
+ var linked_repeater_obj = analog_repeaters.find(function (obj) {
+ return obj.callsign === linked_repeater;
+ });
+
+ if (linked_repeater_obj) {
+ var link = L.polyline([
+ [repeater.latitude, repeater.longitude],
+ [linked_repeater_obj.latitude, linked_repeater_obj.longitude]
+ ], {
+ color: 'gray',
+ weight: 2,
+ opacity: 0.75
+ }).addTo(analog_rpt_map);
+ link.bindPopup(`${repeater.callsign} to ${linked_repeater_obj.callsign}`);
+ }
+ });
+});
+
+// Add a custom CSS style for the markers
+// document.head.insertAdjacentHTML('beforeend', `
+// `
+// );
\ No newline at end of file
diff --git a/templates/base.html b/templates/base.html
index 4a31c42..2e7c1f0 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -58,6 +58,11 @@
{% endfor %}
+ {# Allow pages to import custom Head JS if they need #}
+ {% for url in extra.head_js_import | default(value=[]) %}
+
+ {% endfor %}
+
{% endblock head %}