PSK Reporterで2つの受信局を比較する

あなたは拡大鏡アイコンをクリックして、地図にズームインすることが出来ます。

import cartopy
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import shapely.geometry as sgeom
import cartopy.io.shapereader as shpreader
import matplotlib.pyplot as plt
import numpy as np
import re
import datetime

def maiden2lonlat(maiden: str):
    n = len(maiden)
    maiden = maiden.lower()
    aaa = ord('a')
    lon = -180.0
    lat = -90.0

    lon += (ord(maiden[0])-aaa)*20.0
    lat += (ord(maiden[1])-aaa)*10.0
    lon += int(maiden[2])*2.0
    lat += int(maiden[3])*1.0
    if n >= 6:
        lon += (ord(maiden[4])-aaa) * 5.0/60.0
        lat += (ord(maiden[5])-aaa) * 2.5/60.0
    if n >= 8:
        lon += int(maiden[6]) * 5.0/600.0
        lat += int(maiden[7]) * 2.5/600.0
    return lon, lat
 
def parse_adif(fn):
    raw = re.split('<eor>|<eoh>',open(fn).read() )
    raw.pop(0)
    raw.pop()
    logbook =[]
    for record in raw:
        qso = {}
        tags = re.findall('<(.*?):\d+.*?>([^<\t\n\r\f\v]+)',record)
        for tag in tags:
            qso[tag[0]] = tag[1]
        logbook.append(qso)    
    return logbook

def mydatetime(date, time):
    dt = datetime.datetime(int(date[0:4]), int(date[4:6]), int(date[6:8]), \
                           int(time[0:2]), int(time[2:4]), int(time[4:6]))
    return dt

 
fnames = ['station1.adif', 'station2.adif']

dt0 = datetime.datetime(2001,  1,  1,  0, 0 ,  0)
dt9 = datetime.datetime(2099, 12, 31, 23, 59, 59)

fig = plt.figure(figsize=(16, 9))

fcount = 0
for fn in fnames:
    x = []
    y = []
    r = []
    c = []
    log = parse_adif(fn)
    scount = 0
    for qso in log:
        if ('GRIDSQUARE' in qso):
            dt = mydatetime(qso['QSO_DATE'], qso['TIME_ON'])
            mytime = qso['TIME_ON']
            myhour = float(mytime[0:2])
            if dt >= dt0 and dt <=dt9:
                grid = qso['GRIDSQUARE']
                mylon, mylat = maiden2lonlat(grid)
                if ('APP_PSKREP_SNR' in qso):
                    snr = float(qso['APP_PSKREP_SNR'])
                    print(fcount, scount, grid, mylon, mylat, snr, mytime, myhour)
                    x.append(mylon)
                    y.append(mylat)
                    r.append(50.0+2.0*snr)
                    c.append(myhour/24.0)
                    scount += 1


    cent_lon = 152.5
    ax = fig.add_subplot(2, 3, 1+3*fcount, projection=ccrs.PlateCarree(central_longitude=cent_lon))
    ax.stock_img()
    ax.gridlines()
    ax.coastlines()
    ax.scatter(np.subtract(x,cent_lon), y, c=c, s=r, cmap='hsv', alpha=0.7)

    cent_lon = 139.7
    cent_lat = 35.7
    ax = fig.add_subplot(2, 3, 2+3*fcount, projection=ccrs.AzimuthalEquidistant(central_longitude=cent_lon, central_latitude=cent_lat))
    ax.stock_img()
    ax.gridlines()
    ax.coastlines()
    ax.scatter(x, y, c=c, s=r, cmap='hsv', alpha=0.7, transform=ccrs.Geodetic())

    ax = fig.add_subplot(2, 3, 3+3*fcount, projection=ccrs.PlateCarree())
    ax.stock_img()
    ax.coastlines()
    ax.set_extent([-130.0, -66.5, 20.0, 50.0], ccrs.Geodetic())
    shapename = 'admin_1_states_provinces_lakes_shp'
    states_shp = shpreader.natural_earth(resolution='110m', category='cultural', name=shapename)

    for state in shpreader.Reader(states_shp).geometries():
        facecolor = [0.9375, 0.9375, 0.859375]
        edgecolor = 'black'
        ax.add_geometries([state], ccrs.PlateCarree(),
                          facecolor=facecolor, edgecolor=edgecolor, alpha=0.1)

    ax.scatter(x, y, c=c, s=r, cmap='hsv', alpha=0.9)
    fcount += 1

plt.show()

PSK Reporterから自前で地図を描く(2)

もし、あなたが日本に住んでいるのならこの方が見やすいかもしれません。

cent_lon = 152.5
ax = fig.add_subplot(1, 1, 1, projection=ccrs.PlateCarree(central_longitude=cent_lon))
x.append(mylon-cent_lon)

色は1日の中の時間を表しています。点の大きさはSNRです。

カラーマップは、”hsv”です。左端が00Zに、右端が23Zに対応しています。

cent_lon = 139.7
cent_lat = 35.7
ax = fig.add_subplot(1, 1, 1, projection=ccrs.AzimuthalEquidistant(central_longitude=cent_lon, central_latitude=cent_lat))
x.append(mylon)
ax.scatter(x, y, c=c, s=r, cmap='hsv', alpha=0.7, transform=ccrs.Geodetic())

ax.set_extent([-135, -66.5, 20, 55], ccrs.Geodetic())
shapename = 'admin_1_states_provinces_lakes_shp'
states_shp = shpreader.natural_earth(resolution='110m', category='cultural', name=shapename)

for state in shpreader.Reader(states_shp).geometries():
    facecolor = [0.9375, 0.9375, 0.859375]
    edgecolor = 'black'
    ax.add_geometries([state], ccrs.PlateCarree(),      facecolor=facecolor, edgecolor=edgecolor, alpha=0.1)

PSK Reporterから自前で地図を描く

丸の色と大きさは、受信されたFT8信号のSNRを表しています。

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np
import re
import datetime

def maiden2lonlat(maiden: str):
    n = len(maiden)
    maiden = maiden.lower()
    print(maiden, n)
    aaa = ord('a')
    lon = -180.0
    lat = -90.0

    lon += (ord(maiden[0])-aaa)*20.0
    lat += (ord(maiden[1])-aaa)*10.0
    lon += int(maiden[2])*2.0
    lat += int(maiden[3])*1.0
    if n >= 6:
        lon += (ord(maiden[4])-aaa) * 5.0/60.0
        lat += (ord(maiden[5])-aaa) * 2.5/60.0
    if n >= 8:
        lon += int(maiden[6]) * 5.0/600.0
        lat += int(maiden[7]) * 2.5/600.0
    return lon, lat


ax = plt.axes(projection=ccrs.PlateCarree())
ax.stock_img()
 
fnames = ['station1.adif']
 
def parse_adif(fn):
    raw = re.split('<eor>|<eoh>',open(fn).read() )
    raw.pop(0)
    raw.pop()
    logbook =[]
    for record in raw:
        qso = {}
        tags = re.findall('<(.*?):\d+.*?>([^<\t\n\r\f\v]+)',record)
        for tag in tags:
            qso[tag[0]] = tag[1]
        logbook.append(qso)    
    return logbook

def mydatetime(date, time):
    dt = datetime.datetime(int(date[0:4]), int(date[4:6]), int(date[6:8]), \
                           int(time[0:2]), int(time[2:4]), int(time[4:6]))
    return dt

dt0 = datetime.datetime(2001,  1,  1,  0, 0 ,  0)
dt9 = datetime.datetime(2099, 12, 31, 23, 59, 59)

x = []
y = []
r = []
c = []
fcount = 0
for fn in fnames:
    log = parse_adif(fn)
    scount = 0
    for qso in log:
        if ('GRIDSQUARE' in qso):
            dt = mydatetime(qso['QSO_DATE'], qso['TIME_ON'])
            if dt >= dt0 and dt <=dt9:
                grid = qso['GRIDSQUARE']
                mylon, mylat = maiden2lonlat(grid)
                if ('APP_PSKREP_SNR' in qso):
                    snr = float(qso['APP_PSKREP_SNR'])
                    print(fcount, scount, grid, mylon, mylat, snr)
                    x.append(mylon)
                    y.append(mylat)
                    r.append(50.0+2.0*snr)
                    c.append(-snr*30.0)
                    scount += 1
    fcount += 1
plt.scatter(x, y, c=c, s=r, cmap='hsv', alpha=0.5)
plt.show()

上の図では、大きさはSNRを、色は1日の時間を表しています。

カラーマップは、”jet”という名称のものを使いました。

左端が00Zに、右端が23Zに対応します。

PSK ReporterとCartopyパッケージ

Cartopyは、地理空間情報を処理して地図を生成するなどのデータ解析を行うためのPythonのパッケージです。

Basemapは、Cartopyプロジェクトに置き換えられます。新規のソフトウェア開発は、可能な限りCartopyを用いるべきです。

import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import numpy as np

ax = plt.axes(projection=ccrs.PlateCarree())
ax.stock_img()

n=50
x = 360.0 * np.random.rand(n) - 180.0
y = 120.0 * np.random.rand(n) -  60.0
r =  50.0 * np.random.rand(n) +  20.0

plt.scatter(x, y, c=x, s=r, cmap='hsv', alpha=0.75)
plt.show()

PSK ReporterとBasemapツールキット

PSK Reporterが提供する地図は、サイズとオフセットが制御し難いので、私はMatplotlibのBasemapツールキットを用いて、サイトからダウンロード可能なADIFファイルから、自分で地図を描こうと思いました。

<FREQ:8>7.074812
<MODE:3>FT8
<OPERATOR:6>******
<MY_GRIDSQUARE:6>++++++
<QSO_DATE:8>20181227
<TIME_ON:6>140700
<APP_PSKREP_SNR:3:N>-14
<CALL:6>******
<COUNTRY:13>United States
<DXCC:3>291
<GRIDSQUARE:8>++++++++
<QSO_COMPLETE:3>NIL
<SWL:1>1
<eor>

私が必要なのはMaidenheadグリッドから、緯度・経度のペアに変換するPythonライブラリです。

WSJT-XとRaspberry Pi 3を最初から

pi@raspberrypi:~ $ ls -l ~/Downloads/
-rw-r--r-- 1 pi pi 9999942 Dec 27 09:33 wsjtx_2.0.0_armhf.deb
pi@raspberrypi:~ $ sudo apt install libqt5multimedia5-plugins libqt5serialport5 libqt5sql5-sqlite libfftw3-single3
pi@raspberrypi:~/Downloads $ sudo dpkg -i wsjtx_2.0.0_armhf.deb
pi@raspberrypi:~ $ cat /etc/modules
snd-aloop
pi@raspberrypi:~ $ sudo modprobe snd_aloop
pi@raspberrypi:~ $ lsmod
Module                  Size  Used by
snd_aloop              24576  4
pi@raspberrypi:~ $ aplay -l
**** List of PLAYBACK Hardware Devices ****
card 0: ALSA [bcm2835 ALSA], device 0: bcm2835 ALSA [bcm2835 ALSA]
  Subdevices: 7/7
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
card 0: ALSA [bcm2835 ALSA], device 1: bcm2835 ALSA [bcm2835 IEC958/HDMI]
  Subdevices: 1/1
  Subdevice #0: subdevice #0
card 1: Loopback [Loopback], device 0: Loopback PCM [Loopback PCM]
  Subdevices: 8/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
card 1: Loopback [Loopback], device 1: Loopback PCM [Loopback PCM]
  Subdevices: 7/8
  Subdevice #0: subdevice #0
  Subdevice #1: subdevice #1
  Subdevice #2: subdevice #2
  Subdevice #3: subdevice #3
  Subdevice #4: subdevice #4
  Subdevice #5: subdevice #5
  Subdevice #6: subdevice #6
  Subdevice #7: subdevice #7
pi@raspberrypi:~ $ 

Airspy HF+とRaspberry Pi 3を最初から

最初から、やって見ましょう。

Etcherは、SDカードの書き込みツールです。Mac OS、Linux、Windowsの何でも走ります。

Mac-mini:~ user1$ ssh pi@raspberrypi.local
pi@raspberrypi.local's password: 
Linux raspberrypi 4.14.79-v7+ #1159 SMP Sun Nov 4 17:50:20 GMT 2018 armv7l
Last login: Wed Dec 26 23:23:15 2018
pi@raspberrypi:~ $ df -h
Filesystem      Size  Used Avail Use% Mounted on
/dev/root        30G  4.5G   24G  16% /
devtmpfs        460M     0  460M   0% /dev
tmpfs           464M     0  464M   0% /dev/shm
tmpfs           464M   18M  446M   4% /run
tmpfs           5.0M  4.0K  5.0M   1% /run/lock
tmpfs           464M     0  464M   0% /sys/fs/cgroup
/dev/mmcblk0p1   44M   23M   22M  51% /boot
tmpfs            93M     0   93M   0% /run/user/1000
pi@raspberrypi:~ $ sudo apt-get update
Hit:1 http://archive.raspberrypi.org/debian stretch InRelease                              
Hit:2 http://raspbian.raspberrypi.org/raspbian stretch InRelease                           
Reading package lists... Done                      
pi@raspberrypi:~ $ sudo apt-get upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
pi@raspberrypi:~ $ sudo apt-get dist-upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Calculating upgrade... Done
0 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
pi@raspberrypi:~ $ ls -l ~/Downloads/
total 968
-rw-r--r-- 1 pi pi 984932 Dec 27 02:08 gqrx-sdr-2.11.5-linux-rpi3.tar.xz
pi@raspberrypi:~ $ tar Jxfv gqrx-sdr-2.11.5-linux-rpi3.tar.xz
pi@raspberrypi:~/gqrx-sdr-2.11.5-linux-rpi3 $ sudo apt update
pi@raspberrypi:~/gqrx-sdr-2.11.5-linux-rpi3 $ sudo apt install gnuradio libvolk1-bin libusb-1.0-0 gr-iqbal
pi@raspberrypi:~/gqrx-sdr-2.11.5-linux-rpi3 $ sudo apt install qt5-default libqt5svg5 libportaudio2
pi@raspberrypi:~/gqrx-sdr-2.11.5-linux-rpi3 $ sudo cp ./udev/52-airspyhf.rules /etc/udev/rules.d/
pi@raspberrypi:~ $ dmesg | grep -i airspy
[ 2830.069314] usb 1-1.4: Product: AIRSPY HF+
[ 2830.069323] usb 1-1.4: Manufacturer: www.airspy.com
[ 2830.069331] usb 1-1.4: SerialNumber: AIRSPYHF SN: ..............
pi@raspberrypi:~/gqrx-sdr-2.11.5-linux-rpi3 $ ./gqrx

Airspy HF+とRaspberry Pi 3でFT8(3)

Webスクレイピングのために、何かWebDriverと、python関連のソフトウェアをインストールします。

WebDriver is an open source tool for automated testing of webapps across many browsers. It provides capabilities for navigating to web pages, user input, JavaScript execution, and more. ChromeDriver is a standalone server which implements WebDriver’s wire protocol for Chromium.

pi@raspberrypi:~/Zpython3 $ sudo apt-get install chromium-browser
pi@raspberrypi:~/Zpython3 $ sudo apt-get install chromium-chromedriver
pi@raspberrypi:~/Zpython3 $ /usr/lib/chromium-browser/chromium-browser --version
Chromium 65.0.3325.181 
pi@raspberrypi:~/Zpython3 $ /usr/lib/chromium-browser/chromedriver --version
ChromeDriver 2.35 (0)

pi@raspberrypi:~/Zpython3 $ wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryconda3-2.0.0-Linux-armv7l.sh

pi@raspberrypi:~/Zpython3 $ which python3
/home/pi/berryconda3/bin/python3
pi@raspberrypi:~/Zpython3 $ python3 -V
Python 3.6.1

pi@raspberrypi:~/Zpython3 $ pip3 list
Package      Version  
------------ ---------
selenium     3.141.0  
pi@raspberrypi:~/Zpython3 $ pip3 show selenium
Name: selenium
Version: 3.141.0
Summary: Python bindings for Selenium
Home-page: https://github.com/SeleniumHQ/selenium/
Author: UNKNOWN
Author-email: UNKNOWN
License: Apache 2.0
Location: /home/pi/berryconda3/lib/python3.6/site-packages
Requires: urllib3
Required-by: 

pi@raspberrypi:~/Zpython3 $ python3 selenium01.py
import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options

chrome_option = webdriver.ChromeOptions()
chrome_option.add_argument('--headless')
driver = webdriver.Chrome('/usr/lib/chromium-browser/chromedriver', chrome_options=chrome_option)

for irep in range(360):
    driver.get('https://pskreporter.info/pskmap.html?preset&callsign=ja1a&band=6000000-8000000&timerange=86400')
    assert 'Display Reception Reports' in driver.title
    time.sleep(200)
    fname = time.strftime("%a%d%b%Y%H%M%S.png", time.gmtime())
    print(fname)
    driver.save_screenshot(fname)
    time.sleep(100)

driver.quit()

Airspy HF+とRaspberry Pi 3でFT8(2)

WSJT-X v2.0.0に、アップデートしました。

pi@raspberrypi:~ $ lsb_release -a
No LSB modules are available.
Distributor ID:	Raspbian
Description:	Raspbian GNU/Linux 9.6 (stretch)
Release:	9.6
Codename:	stretch
pi@raspberrypi:~ $ uname -a
Linux raspberrypi 4.14.34-v7+ #1110 SMP Mon Apr 16 15:18:51 BST 2018 armv7l GNU/Linux
pi@raspberrypi:~ $ cat /proc/version
Linux version 4.14.34-v7+ (dc4@dc4-XPS13-9333) (gcc version 4.9.3 (crosstool-NG crosstool-ng-1.22.0-88-g8460611)) #1110 SMP Mon Apr 16 15:18:51 BST 2018

PSK ReporterとWebスクレイピング

スクリーンを3分おきにキャプチャして、GIFアニメーションファイルを生成しています。

import time
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.options import Options
 
options = Options()
options.binary_location = '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary'
options.add_argument('--headless')
driver = webdriver.Chrome('/Users/user1/Downloads/chromedriver', chrome_options=options)
 
for irep in range(10000):
    driver.get('https://pskreporter.info/pskmap.html?preset&callsign=ja1a&band=6000000-8000000&timerange=86400')
    assert 'Display Reception Reports' in driver.title
    time.sleep(120)
    fname = time.strftime("%a%d%b%Y%H%M%S.png", time.gmtime())
    print(fname)
    driver.save_screenshot(fname)
    time.sleep(60)
 
driver.quit()
$ mkdir ./cropped
$ mogrify -crop 800x432+0+128! -path ./cropped *.png
$ convert -delay 1 -loop 0 ./cropped/*.png movie.gif