Azimuth Map - Bearings for Remaining DXCC contacts.


In [1]:
%matplotlib inline
# %config InlineBackend.figure_format = 'svg'
from IPython.core.pylabtools import figsize
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
import numpy as np
import adif
from math import pi,radians
from mlocs import toLoc
from azutil import to_polar, relative_offset_qth, all_dxcc, loc_for_dxcc
import seaborn
figsize(9,9)

Set these variables :

  • my_qth - to your current operating location
  • my_log_file - to the path to the ADIF file to use
  • all_qths - a list of locators to plot as places of interest on the graph.

In [2]:
my_qth = 'IO91vk'
my_log_file = 'log.adi' #'contacts.adi'
all_qths = [ my_qth ] #,'CM86xx','DO13' ] # list of locations you operate from uses first as center
print ('My QTH: {}, all QTHs: {}.'.format(my_qth,', '.join(all_qths)))


My QTH: IO91vk, all QTHs: IO91vk.

In [3]:
def draw_qth(m,locs,marker='o',**kwargs):
    ''' Draw a series of locations on m. locs is array of maidenhead locators.'''
    coords = [ toLoc(loc) for loc in locs   ]
    coords = [ m(c[1],c[0]) for c in coords ] # transpose to map coords
    for x,y in coords:
        m.plot(x,y,marker,linestyle='None',**kwargs)
    pass

def draw_circle(m, qth0, qth_list, **kwargs):
    '''draw a great circle 'arc' to each locator in qth_list originating at qth0'''
    
    defaults = {'alpha' :0.75, 'color':'lightblue','linewidth':0.5}
    
    lat_0,lon_0 = toLoc(qth0)
    
    coords = [ toLoc(loc) for loc in qth_list ]
    
    for k,v in defaults.iteritems():
        if k not in kwargs:
            kwargs[k] = v

    for lon,lat in [ (e[1], e[0]) for e in coords]:
        m.drawgreatcircle (lon_0,lat_0,lon,lat,**kwargs)

Let's load my log from ADIF file.

  • all_contact_dxcc are the DXCC entities (in a set() ) that I've contacted.
  • contact_grids - are all the grids mentioned in the log.
  • plotted_by_grid are dxccs that I've plotted by gridsquare provided (vs entity coordinates)
  • no_grid - ones for which I recorded no grid

Alternate Approach - build a dictionary of DXCC entities with list of grids. None if no GS. Add defaults. Average.


In [4]:
adif_reader = adif.ADIFReader(open (my_log_file,'rU'))
ad_contacts = adif_reader.load()

'''TODO clean up extras'''

ad_all_contact_dxcc = set([ q['dxcc']       for q in ad_contacts if 'dxcc' in q])
ad_contact_grids    = set([ q['gridsquare'] for q in ad_contacts if 'gridsquare' in q])
ad_plotted_by_grid  = set([ q['dxcc']       for q in ad_contacts if 'gridsquare' in q])
ad_plot_by_dxcc = ad_all_contact_dxcc.difference(ad_plotted_by_grid)

ad_dxcc_grids = [ loc_for_dxcc(d) for d in ad_plot_by_dxcc ]
#ad_no_grid    = [ q for q in ad_contacts if 'gridsquare' not in q ]

all_dxcc_locs =  [ loc_for_dxcc(d) for d in all_dxcc() ]
ctc_dxcc_locs = [ loc_for_dxcc(d) for d in ad_all_contact_dxcc ]

In [5]:
def azmap(qth):
    proj='aeqd'
    #proj='ortho'
    lat,lon = toLoc(my_qth)
    m = Basemap(projection=proj,lat_0=lat,lon_0=lon)
    m.drawmapboundary()
    m.drawcountries(linewidth=0.25)
    m.drawcoastlines(linewidth=0.5)
    m.drawparallels(np.arange(-80.0,81,20),linewidth=0.25)
    m.drawmeridians(np.arange(-180,180,20),linewidth=0.25)
    if False: m.nightshade(dt.datetime.utcnow(),alpha=0.2)
    plt.title(qth)
    return m

Draw Contact Map

Now we can draw a map in Azimuth Equidistant Projection centered on our QTH and plot contacts by grid, then by DXCC.


In [6]:
print ('Number of grid contacts: \t{}\nNumber of DXCC contacts: \t{}\nTotal Contacts: \t\t{}'.format(
    len(ad_contact_grids),
    len(ad_dxcc_grids),
    len(ad_contacts)
    ))


Number of grid contacts: 	359
Number of DXCC contacts: 	5
Total Contacts: 		431

In [7]:
m = azmap(my_qth)

# Draw GCR lines to each contact
draw_circle(m, my_qth, ad_contact_grids)
draw_circle(m, my_qth, ad_dxcc_grids)

# Draw our markers for contacts.
draw_qth( m, ad_contact_grids,   color='blue',alpha=0.5)
draw_qth( m, all_qths,           color='red', alpha=0.5, marker='8')
draw_qth( m, ad_dxcc_grids, alpha=0.5, marker='h',color='yellow')
title_text = plt.title('Contacts - {}'.format(my_qth))


Number of DXCC by Azimuth Angle

Let's just draw all the DXCC from where I am to see where the 'action' might be. Blue is everything, red are contacts with DXCCs that are in the log file.


In [8]:
m = azmap(my_qth)
plt.title('All DXCC from {}'.format(my_qth))

draw_circle(m, my_qth, all_dxcc_locs)
draw_qth(m,all_dxcc_locs,color='blue',alpha=0.5)
draw_qth(m,ctc_dxcc_locs,color='red',alpha=0.5)


These functions let us build a dictionary of all contacts per DXCC so we can average the location for the single bearing we will use to represent that DXCC on the final polar histogram.


In [9]:
def insert_ctc(d, dx,lonlat):
    if dx not in d:
        d[dx] = list()
    d[dx].append( np.array([ lonlat[1],lonlat[0] ] ) )

ctc_dict = dict()
no_dx = 0
for c in ad_contacts:
    if 'dxcc' in c:
        dx = int(c['dxcc'])
        if dx > 0:   
            if 'gridsquare' in c:
                g = c['gridsquare']
            else:
                g = loc_for_dxcc(dx)
            insert_ctc(ctc_dict,dx,toLoc(g))
        else:
            no_dx += 1
            print ('{} has no DXCC'.format(c['callsign']))

# Average each DXCC co-ordinate list to a single lat lon
for dx in ctc_dict:
    ctc_dict[dx] = np.mean(ctc_dict[dx],axis=0)

In [10]:
ctc_bearings =  [ to_polar(*relative_offset_qth(m,my_qth,lonlat_dest=ctc_dict[dx]))[1] for dx       in ctc_dict ]
dxcc_bearings = [ to_polar(*relative_offset_qth(m,my_qth,grid_dest=dxcc_loc))[1]       for dxcc_loc in all_dxcc_locs ]
print 'Contacted DXCCs : ',len(ctc_bearings),'/', len(dxcc_bearings)


Contacted DXCCs :  61 / 340

Bin the bearings into n-bins and plot bar chart (polar)

  • dxcc_data <- number of DXCC entities in that bin (azimuth bucket)
  • ctc_data <- number of DXCC entities in that bin in log file.

Note : ctc_data can exceed the DXCC number if you've moved your QTH over the course of the log.


In [11]:
nbins = 16

def setup_polar_plot():
    polar=True
    ax = plt.subplot(111,polar=polar)
    if polar:
        ax.set_theta_zero_location('N')
        ax.set_theta_direction(-1)
        ax.set_theta_direction('clockwise')
        ax.set_theta_zero_location('N')
    else:
        ax.set_yscale('log')
    return ax


def bars(ax,theta,r,**kwargs):
    w = 2*pi/len(theta)*0.95
    ax.bar(left=theta,height=r,width=w,alpha=0.5,linewidth=0,align='center',**kwargs)
    
def bins(nbins=nbins):
#    return np.linspace(0,2*pi,nbins)
    return np.linspace(pi/nbins,(2*pi)-(pi/nbins),nbins)

def to_bin(bearing,nbins=nbins):
    bsz = (2*pi)/nbins
    return (bearing/bsz)%nbins

In [12]:
from math import degrees

dxcc_data = np.empty(nbins)
dxcc_data.fill(0)

ctc_data = np.empty(nbins)
ctc_data.fill(0)

''' Accumulate DXCCs by bearing '''

for theta in dxcc_bearings:
    dxcc_data[to_bin(theta)] += 1

for theta in ctc_bearings:
    ctc_data[to_bin(theta)] += 1

In [13]:
ax = setup_polar_plot()
bars(ax,bins(), ctc_data,color='red')
bars(ax,bins(), dxcc_data)
p = plt.title('Number of DXCCs by Azimuth Angle from {}\n'.format(my_qth))
l = plt.legend(['Contacts','All DXCC'])



In [14]:
ax = setup_polar_plot()
bars(ax,bins(), np.log10([ x+0.49 for x in ctc_data]),color='red')
bars(ax,bins(), np.log10(dxcc_data))
p = plt.title('Number of DXCCs ($log_{{10}}$) by Azimuth Angle from {}\n'.format(my_qth))
l = plt.legend(['My Contacts','All DXCC'])