mirror of
https://github.com/permissionlesstech/georelays.git
synced 2026-07-25 21:05:18 +00:00
Add relay location maps and automation workflow
This commit is contained in:
@@ -0,0 +1,57 @@
|
|||||||
|
name: Update Relay Maps
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Run after the "Update Relay Data" workflow completes
|
||||||
|
workflow_run:
|
||||||
|
workflows: ["Update Relay Data"]
|
||||||
|
types:
|
||||||
|
- completed
|
||||||
|
# Also allow manual triggering
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-relay-maps:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event.workflow_run.conclusion == 'success' || github.event_name == 'workflow_dispatch' }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 100 # Need deeper history
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install Python dependencies
|
||||||
|
run: |
|
||||||
|
python -m pip install --upgrade pip
|
||||||
|
pip install matplotlib pandas numpy folium scipy
|
||||||
|
|
||||||
|
- name: Generate relay maps
|
||||||
|
run: |
|
||||||
|
mkdir -p assets
|
||||||
|
|
||||||
|
# Execute the script
|
||||||
|
python scripts/generate_relay_map.py
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: git-check
|
||||||
|
run: |
|
||||||
|
git add assets/relay_locations_static.png assets/relay_locations_heatmap.png assets/relay_locations_interactive.html
|
||||||
|
git diff --staged --exit-code || echo "changes=true" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Commit and push changes
|
||||||
|
if: steps.git-check.outputs.changes == 'true'
|
||||||
|
run: |
|
||||||
|
git config --local user.email "action@github.com"
|
||||||
|
git config --local user.name "GitHub Action"
|
||||||
|
git commit -m "Update relay location maps - $(date -u)"
|
||||||
|
git push
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 192 KiB |
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
After Width: | Height: | Size: 251 KiB |
Executable
+263
@@ -0,0 +1,263 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Generate World Map of Nostr Relay Locations
|
||||||
|
|
||||||
|
This script reads the nostr_relays.csv file, which contains information about
|
||||||
|
BitChat-compatible Nostr relays, including their geographical coordinates.
|
||||||
|
It generates both an interactive HTML map and a static PNG image showing
|
||||||
|
the distribution of relays around the world.
|
||||||
|
|
||||||
|
The maps are saved in the assets directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import pandas as pd
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import folium
|
||||||
|
from folium.plugins import MarkerCluster
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib.colors import LinearSegmentedColormap
|
||||||
|
import time
|
||||||
|
|
||||||
|
def create_interactive_map(df, output_path):
|
||||||
|
"""
|
||||||
|
Create an interactive HTML map showing relay locations with clustering.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame containing relay data with Latitude and Longitude columns
|
||||||
|
output_path: Path to save the HTML map
|
||||||
|
"""
|
||||||
|
# Create map centered at (0, 0) with zoom level 2
|
||||||
|
world_map = folium.Map(location=[0, 0], zoom_start=2, tiles='CartoDB positron')
|
||||||
|
|
||||||
|
# Add marker cluster
|
||||||
|
marker_cluster = MarkerCluster().add_to(world_map)
|
||||||
|
|
||||||
|
# Add markers for each relay
|
||||||
|
for idx, row in df.iterrows():
|
||||||
|
folium.Marker(
|
||||||
|
location=[row['Latitude'], row['Longitude']],
|
||||||
|
popup=row['Relay URL'],
|
||||||
|
icon=folium.Icon(color='blue', icon='signal', prefix='fa')
|
||||||
|
).add_to(marker_cluster)
|
||||||
|
|
||||||
|
# Save map
|
||||||
|
world_map.save(output_path)
|
||||||
|
|
||||||
|
return len(df)
|
||||||
|
|
||||||
|
def create_static_map(df, output_path):
|
||||||
|
"""
|
||||||
|
Create a static PNG map showing relay locations on a simple world map.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame containing relay data with Latitude and Longitude columns
|
||||||
|
output_path: Path to save the PNG map
|
||||||
|
"""
|
||||||
|
plt.figure(figsize=(15, 10))
|
||||||
|
|
||||||
|
# Create a simple base map
|
||||||
|
ax = plt.axes()
|
||||||
|
ax.set_facecolor('#DDEEFF') # Light blue background for oceans
|
||||||
|
|
||||||
|
# Draw a simple grid
|
||||||
|
for i in range(-180, 181, 30):
|
||||||
|
plt.axvline(x=i, color='#CCCCCC', linestyle='--', alpha=0.5)
|
||||||
|
|
||||||
|
for i in range(-90, 91, 30):
|
||||||
|
plt.axhline(y=i, color='#CCCCCC', linestyle='--', alpha=0.5)
|
||||||
|
|
||||||
|
# Set map limits
|
||||||
|
plt.xlim(-180, 180)
|
||||||
|
plt.ylim(-90, 90)
|
||||||
|
|
||||||
|
# Plot relay locations
|
||||||
|
plt.scatter(
|
||||||
|
df['Longitude'],
|
||||||
|
df['Latitude'],
|
||||||
|
alpha=0.7,
|
||||||
|
c='blue',
|
||||||
|
s=30,
|
||||||
|
edgecolor='white',
|
||||||
|
linewidth=0.5
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add title and labels
|
||||||
|
plt.title('Global Distribution of BitChat-Compatible Nostr Relays', fontsize=16)
|
||||||
|
|
||||||
|
# Add timestamp
|
||||||
|
timestamp = time.strftime("%Y-%m-%d", time.localtime())
|
||||||
|
plt.annotate(
|
||||||
|
f'Generated: {timestamp} | Total Relays: {len(df)}',
|
||||||
|
xy=(0.02, 0.02),
|
||||||
|
xycoords='axes fraction',
|
||||||
|
fontsize=10,
|
||||||
|
bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.8)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add labels for equator and prime meridian
|
||||||
|
plt.text(0, -5, "0° (Prime Meridian)", ha='center', fontsize=8, alpha=0.7)
|
||||||
|
plt.text(5, 0, "0° (Equator)", va='center', rotation=90, fontsize=8, alpha=0.7)
|
||||||
|
|
||||||
|
# Remove axes ticks but keep latitude/longitude labels at 30-degree intervals
|
||||||
|
plt.xticks([-180, -150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180],
|
||||||
|
["180°W", "150°W", "120°W", "90°W", "60°W", "30°W", "0°",
|
||||||
|
"30°E", "60°E", "90°E", "120°E", "150°E", "180°E"],
|
||||||
|
fontsize=8)
|
||||||
|
plt.yticks([-90, -60, -30, 0, 30, 60, 90],
|
||||||
|
["90°S", "60°S", "30°S", "0°", "30°N", "60°N", "90°N"],
|
||||||
|
fontsize=8)
|
||||||
|
|
||||||
|
# Adjust layout and save
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
return len(df)
|
||||||
|
|
||||||
|
def create_heatmap(df, output_path):
|
||||||
|
"""
|
||||||
|
Create a heatmap visualization showing relay density across the world.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
df: DataFrame containing relay data with Latitude and Longitude columns
|
||||||
|
output_path: Path to save the PNG heatmap
|
||||||
|
"""
|
||||||
|
plt.figure(figsize=(15, 10))
|
||||||
|
|
||||||
|
# Create a simple base map
|
||||||
|
ax = plt.axes()
|
||||||
|
ax.set_facecolor('#DDEEFF') # Light blue background for oceans
|
||||||
|
|
||||||
|
# Draw a simple grid
|
||||||
|
for i in range(-180, 181, 30):
|
||||||
|
plt.axvline(x=i, color='#CCCCCC', linestyle='--', alpha=0.5)
|
||||||
|
|
||||||
|
for i in range(-90, 91, 30):
|
||||||
|
plt.axhline(y=i, color='#CCCCCC', linestyle='--', alpha=0.5)
|
||||||
|
|
||||||
|
# Set map limits
|
||||||
|
plt.xlim(-180, 180)
|
||||||
|
plt.ylim(-90, 90)
|
||||||
|
|
||||||
|
# Create grid for heatmap
|
||||||
|
x = np.linspace(-180, 180, 360)
|
||||||
|
y = np.linspace(-90, 90, 180)
|
||||||
|
|
||||||
|
# Count relays in each grid cell
|
||||||
|
heatmap = np.zeros((len(y)-1, len(x)-1))
|
||||||
|
|
||||||
|
for _, row in df.iterrows():
|
||||||
|
lon_idx = np.searchsorted(x, row['Longitude']) - 1
|
||||||
|
lat_idx = np.searchsorted(y, row['Latitude']) - 1
|
||||||
|
|
||||||
|
if 0 <= lon_idx < len(x)-1 and 0 <= lat_idx < len(y)-1:
|
||||||
|
heatmap[lat_idx, lon_idx] += 1
|
||||||
|
|
||||||
|
# Apply Gaussian smoothing to heatmap
|
||||||
|
from scipy.ndimage import gaussian_filter
|
||||||
|
heatmap = gaussian_filter(heatmap, sigma=3)
|
||||||
|
|
||||||
|
# Create custom colormap (blue to white)
|
||||||
|
colors = [(0, 0, 0.8, 0), (0, 0, 1, 0.7), (0.5, 0.5, 1, 0.8), (1, 1, 1, 0.9)]
|
||||||
|
cmap = LinearSegmentedColormap.from_list('custom_blue', colors)
|
||||||
|
|
||||||
|
# Plot heatmap
|
||||||
|
plt.pcolormesh(x, y, heatmap, cmap=cmap, alpha=0.7)
|
||||||
|
|
||||||
|
# Add title
|
||||||
|
plt.title('Global Heatmap of BitChat-Compatible Nostr Relays', fontsize=16)
|
||||||
|
|
||||||
|
# Add timestamp and relay count
|
||||||
|
timestamp = time.strftime("%Y-%m-%d", time.localtime())
|
||||||
|
plt.annotate(
|
||||||
|
f'Generated: {timestamp} | Total Relays: {len(df)}',
|
||||||
|
xy=(0.02, 0.02),
|
||||||
|
xycoords='axes fraction',
|
||||||
|
fontsize=10,
|
||||||
|
bbox=dict(boxstyle="round,pad=0.3", fc="white", alpha=0.8)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add labels for equator and prime meridian
|
||||||
|
plt.text(0, -5, "0° (Prime Meridian)", ha='center', fontsize=8, alpha=0.7)
|
||||||
|
plt.text(5, 0, "0° (Equator)", va='center', rotation=90, fontsize=8, alpha=0.7)
|
||||||
|
|
||||||
|
# Add longitude/latitude labels
|
||||||
|
plt.xticks([-180, -150, -120, -90, -60, -30, 0, 30, 60, 90, 120, 150, 180],
|
||||||
|
["180°W", "150°W", "120°W", "90°W", "60°W", "30°W", "0°",
|
||||||
|
"30°E", "60°E", "90°E", "120°E", "150°E", "180°E"],
|
||||||
|
fontsize=8)
|
||||||
|
plt.yticks([-90, -60, -30, 0, 30, 60, 90],
|
||||||
|
["90°S", "60°S", "30°S", "0°", "30°N", "60°N", "90°N"],
|
||||||
|
fontsize=8)
|
||||||
|
|
||||||
|
# Adjust layout and save
|
||||||
|
plt.tight_layout()
|
||||||
|
plt.savefig(output_path, dpi=300, bbox_inches='tight')
|
||||||
|
plt.close()
|
||||||
|
|
||||||
|
return len(df)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""
|
||||||
|
Main function to generate maps of relay locations.
|
||||||
|
"""
|
||||||
|
# Change to the root directory of the project
|
||||||
|
script_dir = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
root_dir = os.path.dirname(script_dir)
|
||||||
|
os.chdir(root_dir)
|
||||||
|
|
||||||
|
# Ensure the assets directory exists
|
||||||
|
os.makedirs('assets', exist_ok=True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Read the relay data
|
||||||
|
df = pd.read_csv('nostr_relays.csv')
|
||||||
|
|
||||||
|
# Filter out rows with missing or invalid coordinates
|
||||||
|
df = df.dropna(subset=['Latitude', 'Longitude'])
|
||||||
|
|
||||||
|
# Filter out invalid coordinates
|
||||||
|
df = df[(df['Latitude'] >= -90) & (df['Latitude'] <= 90) &
|
||||||
|
(df['Longitude'] >= -180) & (df['Longitude'] <= 180)]
|
||||||
|
|
||||||
|
# Generate maps
|
||||||
|
# 1. Interactive HTML map
|
||||||
|
relay_count_interactive = create_interactive_map(
|
||||||
|
df,
|
||||||
|
'assets/relay_locations_interactive.html'
|
||||||
|
)
|
||||||
|
|
||||||
|
# 2. Static PNG map
|
||||||
|
try:
|
||||||
|
relay_count_static = create_static_map(
|
||||||
|
df,
|
||||||
|
'assets/relay_locations_static.png'
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error creating static map: {e}")
|
||||||
|
relay_count_static = 0
|
||||||
|
|
||||||
|
# 3. Heatmap
|
||||||
|
try:
|
||||||
|
from scipy.ndimage import gaussian_filter
|
||||||
|
relay_count_heatmap = create_heatmap(
|
||||||
|
df,
|
||||||
|
'assets/relay_locations_heatmap.png'
|
||||||
|
)
|
||||||
|
except ImportError:
|
||||||
|
print("Could not create heatmap: scipy not installed")
|
||||||
|
relay_count_heatmap = 0
|
||||||
|
|
||||||
|
# Print summary
|
||||||
|
print(f"Generated interactive map with {relay_count_interactive} relays")
|
||||||
|
if relay_count_static > 0:
|
||||||
|
print(f"Generated static map with {relay_count_static} relays")
|
||||||
|
if relay_count_heatmap > 0:
|
||||||
|
print(f"Generated heatmap with {relay_count_heatmap} relays")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error generating relay maps: {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user