#!/usr/bin/env python3

#
# geobin_to_worldmap
# Copyright (C) 2014 by University of Southern California.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License,
# version 2, as published by the Free Software Foundation.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License along
# with this program; if not, write to the Free Software Foundation, Inc.,
# 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
#

#
# This code is derived from Lin Quan's world map plotting code.
# The research version is dirunal_linquan/plot_worldmap_data_frac.py.
# This version very signficantly changed:
# we just render a grayscale on the globe; no data processing.
#



import matplotlib
# from <http://phyletica.org/matplotlib-fonts/> to get TureType instead of Type 3 fonts
if False:
    matplotlib.rcParams['pdf.fonttype'] = 42
    matplotlib.rcParams['ps.fonttype'] = 42
# xxx: no, changes the font to DejaVuSans but it's ugly (super-compressed)
#
# alternative:
# http://jonathansoma.com/lede/data-studio/matplotlib/changing-fonts-in-matplotlib/
#matplotlib.rcParams['font.sans-serif'] = 'Helvetica'
#matplotlib.rcParams['font.family'] = 'sans-serif'
# but helveitc is not supported, according to: <http://jonathansoma.com/lede/data-studio/matplotlib/list-all-fonts-available-in-matplotlib-plus-samples/>
matplotlib.use("Agg")

import argparse
import math

from mpl_toolkits.basemap import Basemap
from matplotlib.patches import Polygon, Rectangle, Circle, Wedge, ConnectionPatch, Ellipse
from matplotlib.font_manager import FontProperties
from datetime import datetime, timedelta
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
import colorsys, re, sys, time

LONGITUDE_MIN = -180.0
LATITUDE_MIN = -90.0

# next is from http://www.sandia.gov/~kmorel/documents/ColorMaps/
moreland_heatmap = [
  "#3b4cc0", "#3c4ec2", "#3d50c3", "#3e51c5", "#3f53c6", "#4055c8", "#4257c9", "#4358cb",
  "#445acc", "#455cce", "#465dcf", "#475fd1", "#4961d2", "#4a63d3", "#4b64d5", "#4c66d6",
  "#4d68d7", "#4f69d9", "#506bda", "#516ddb", "#526edd", "#5470de", "#5572df", "#5673e0",
  "#5775e1", "#5977e2", "#5a78e4", "#5b7ae5", "#5d7be6", "#5e7de7", "#5f7fe8", "#6080e9",
  "#6282ea", "#6383eb", "#6485ec", "#6687ed", "#6788ee", "#688aef", "#6a8bef", "#6b8df0",
  "#6c8ef1", "#6e90f2", "#6f91f3", "#7093f3", "#7294f4", "#7396f5", "#7497f6", "#7699f6",
  "#779af7", "#789cf7", "#7a9df8", "#7b9ef9", "#7ca0f9", "#7ea1fa", "#7fa3fa", "#81a4fb",
  "#82a5fb", "#83a7fc", "#85a8fc", "#86a9fc", "#87abfd", "#89acfd", "#8aadfd", "#8caefe",
  "#8db0fe", "#8eb1fe", "#90b2fe", "#91b3fe", "#93b5ff", "#94b6ff", "#95b7ff", "#97b8ff",
  "#98b9ff", "#99baff", "#9bbbff", "#9cbcff", "#9ebeff", "#9fbfff", "#a0c0ff", "#a2c1ff",
  "#a3c2ff", "#a4c3fe", "#a6c4fe", "#a7c5fe", "#a8c6fe", "#aac7fd", "#abc7fd", "#acc8fd",
  "#aec9fd", "#afcafc", "#b0cbfc", "#b2ccfb", "#b3cdfb", "#b4cdfb", "#b6cefa", "#b7cffa",
  "#b8d0f9", "#b9d0f8", "#bbd1f8", "#bcd2f7", "#bdd2f7", "#bed3f6", "#c0d4f5", "#c1d4f5",
  "#c2d5f4", "#c3d5f3", "#c5d6f3", "#c6d6f2", "#c7d7f1", "#c8d7f0", "#c9d8ef", "#cbd8ee",
  "#ccd9ee", "#cdd9ed", "#ced9ec", "#cfdaeb", "#d0daea", "#d1dbe9", "#d2dbe8", "#d3dbe7",
  "#d5dbe6", "#d6dce5", "#d7dce4", "#d8dce3", "#d9dce1", "#dadce0", "#dbdcdf", "#dcddde",
  "#dddddd", "#dedcdb", "#dfdcda", "#e0dbd8", "#e1dbd7", "#e2dad6", "#e3dad4", "#e4d9d3",
  "#e5d8d1", "#e6d8d0", "#e7d7ce", "#e8d7cd", "#e8d6cb", "#e9d5ca", "#ead4c8", "#ebd4c7",
  "#ecd3c5", "#ecd2c4", "#edd1c2", "#eed1c1", "#eed0bf", "#efcfbe", "#f0cebc", "#f0cdbb",
  "#f1ccb9", "#f1cbb8", "#f2cab6", "#f2c9b5", "#f3c8b3", "#f3c7b2", "#f4c6b0", "#f4c5ae",
  "#f5c4ad", "#f5c3ab", "#f5c2aa", "#f5c1a8", "#f6c0a7", "#f6bfa5", "#f6bea3", "#f6bca2",
  "#f7bba0", "#f7ba9f", "#f7b99d", "#f7b89c", "#f7b69a", "#f7b598", "#f7b497", "#f7b295",
  "#f7b194", "#f7b092", "#f7ae91", "#f7ad8f", "#f7ac8d", "#f7aa8c", "#f7a98a", "#f7a789",
  "#f7a687", "#f6a486", "#f6a384", "#f6a183", "#f6a081", "#f59e7f", "#f59d7e", "#f59b7c",
  "#f49a7b", "#f49879", "#f49778", "#f39576", "#f39375", "#f29273", "#f29072", "#f18e70",
  "#f18d6f", "#f08b6d", "#f0896c", "#ef886a", "#ee8669", "#ee8467", "#ed8266", "#ec8164",
  "#ec7f63", "#eb7d61", "#ea7b60", "#e9795f", "#e9785d", "#e8765c", "#e7745a", "#e67259",
  "#e57058", "#e46e56", "#e36c55", "#e36a53", "#e26852", "#e16651", "#e0644f", "#df624e",
  "#de604d", "#dd5e4b", "#dc5c4a", "#da5a49", "#d95847", "#d85646", "#d75445", "#d65243",
  "#d55042", "#d44e41", "#d24b40", "#d1493e", "#d0473d", "#cf453c", "#cd423b", "#cc4039",
  "#cb3e38", "#ca3b37", "#c83936", "#c73635", "#c63334", "#c43132", "#c32e31", "#c12b30",
  "#c0282f", "#be252e", "#bd222d", "#bc1e2c", "#ba1a2b", "#b91629", "#b71128", "#b50b27",
  "#b40426"]
moreland_light_heatmap = [
  '#7d86c0', '#7d87c1', '#7d88c1', '#7e89c2', '#7e8ac3', '#7e8ac3', '#7f8bc4', '#7f8cc4', 
  '#7f8dc5', '#7f8ec6', '#808fc6', '#8090c7', '#8091c7', '#8191c8', '#8192c9', '#8193c9', 
  '#8294ca', '#8295ca', '#8296cb', '#8397cb', '#8397cc', '#8398cc', '#8499cd', '#849acd', 
  '#859bce', '#859cce', '#859dcf', '#869dcf', '#869ed0', '#879fd0', '#87a0d1', '#88a1d1', 
  '#88a1d1', '#89a2d2', '#89a3d2', '#8aa4d3', '#8aa5d3', '#8ba6d3', '#8ba6d4', '#8ca7d4', 
  '#8ca8d5', '#8da9d5', '#8daad5', '#8eaad6', '#8fabd6', '#8facd6', '#90add7', '#90add7', 
  '#91aed7', '#92afd7', '#92b0d8', '#93b1d8', '#94b1d8', '#94b2d9', '#95b3d9', '#96b4d9', 
  '#96b4d9', '#97b5da', '#98b6da', '#99b7da', '#99b7da', '#9ab8da', '#9bb9db', '#9cb9db', 
  '#9cbadb', '#9dbbdb', '#9ebcdb', '#9fbcdc', '#a0bddc', '#a0bedc', '#a1bedc', '#a2bfdc', 
  '#a3c0dc', '#a4c0dc', '#a5c1dd', '#a6c2dd', '#a6c2dd', '#a7c3dd', '#a8c4dd', '#a9c4dd', 
  '#aac5dd', '#abc6dd', '#acc6dd', '#adc7dd', '#aec7de', '#afc8de', '#b0c9de', '#b1c9de', 
  '#b2cade', '#b3cade', '#b4cbde', '#b5ccde', '#b6ccde', '#b7cdde', '#b8cdde', '#b9cede', 
  '#bacede', '#bbcfde', '#bccfde', '#bdd0de', '#bed1de', '#bfd1de', '#c0d2de', '#c1d2de', 
  '#c2d3de', '#c3d3de', '#c4d4de', '#c5d4de', '#c6d5de', '#c7d5de', '#c8d5de', '#cad6de', 
  '#cbd6de', '#ccd7de', '#cdd7de', '#ced8de', '#cfd8dd', '#d0d9dd', '#d1d9dd', '#d2d9dd', 
  '#d4dadd', '#d5dadd', '#d6dbdd', '#d7dbdd', '#d8dbdd', '#d9dcdd', '#dadcdd', '#dcdcdd', 
  '#dddddd', '#dddcdb', '#dddbda', '#dddbd9', '#dddad8', '#ddd9d7', '#ddd9d6', '#ddd8d4', 
  '#dcd7d3', '#dcd6d2', '#dcd6d1', '#dcd5d0', '#dcd4cf', '#dcd3ce', '#dcd3cc', '#dcd2cb', 
  '#dcd1ca', '#dcd0c9', '#dcd0c8', '#dccfc7', '#dccec6', '#dccdc5', '#dcccc4', '#dcccc2', 
  '#dccbc1', '#dccac0', '#dcc9bf', '#dcc8be', '#dbc8bd', '#dbc7bc', '#dbc6bb', '#dbc5ba', 
  '#dbc4b9', '#dbc3b8', '#dbc3b7', '#dbc2b6', '#dbc1b5', '#dac0b4', '#dabfb3', '#dabeb2', 
  '#dabdb1', '#dabcb0', '#dabcaf', '#dabbae', '#d9baad', '#d9b9ad', '#d9b8ac', '#d9b7ab', 
  '#d9b6aa', '#d9b5a9', '#d8b4a8', '#d8b4a7', '#d8b3a6', '#d8b2a5', '#d7b1a5', '#d7b0a4', 
  '#d7afa3', '#d7aea2', '#d6ada1', '#d6aca1', '#d6aba0', '#d6aa9f', '#d5a99e', '#d5a99d', 
  '#d5a89d', '#d5a79c', '#d4a69b', '#d4a59a', '#d4a49a', '#d3a399', '#d3a298', '#d3a197', 
  '#d2a097', '#d29f96', '#d29e95', '#d19d95', '#d19c94', '#d19b93', '#d09a93', '#d09992', 
  '#cf9891', '#cf9891', '#cf9790', '#ce968f', '#ce958f', '#cd948e', '#cd938e', '#cc928d', 
  '#cc918c', '#cc908c', '#cb8f8b', '#cb8e8b', '#ca8d8a', '#ca8c8a', '#c98b89', '#c98a89', 
  '#c88988', '#c88887', '#c78787', '#c78686', '#c68686', '#c58585', '#c58485', '#c48384', 
  '#c48284', '#c38184', '#c38083', '#c27f83', '#c17e82', '#c17d82', '#c07c81', '#c07b81', 
  '#bf7a80', '#be7980', '#be797f', '#bd787f', '#bc777f', '#bc767e', '#bb757e', '#ba747d', 
  '#ba737d', '#b9727d', '#b8717c', '#b8707c', '#b7707b', '#b66f7b', '#b56e7b', '#b56d7a', 
  '#b46c7a' ]

n_wedges_list = None
# this table is Figure 3 from [Tol10a]
# wedges_color_table:
wedges_color_table = []
wedges_color_table.append([ 'error' ]);
wedges_color_table.append([                   '#4477aa'                                                                                                       ]);
wedges_color_table.append([                   '#4477aa',                                                            '#CC6677'                                 ]);
wedges_color_table.append([                   '#4477aa',                                      '#DDCC77',            '#CC6677'                                  ]);
wedges_color_table.append([                   '#4477aa',                '#117733',            '#DDCC77',            '#CC6677'                                  ]);
wedges_color_table.append([ '#332288',            '#88CCEE',            '#117733',            '#DDCC77',            '#CC6677'                                  ]);
wedges_color_table.append([ '#332288',            '#88CCEE',            '#117733',            '#DDCC77',            '#CC6677',                        '#AA4499' ]);
wedges_color_table.append([ '#332288',            '#88CCEE', '#44AA99', '#117733',            '#DDCC77',            '#CC6677',                        '#AA4499' ]);
wedges_color_table.append([ '#332288',            '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77',            '#CC6677',                        '#AA4499' ]);
wedges_color_table.append([ '#332288',            '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77',            '#CC6677',            '#882255', '#AA4499' ]);
wedges_color_table.append([ '#332288',            '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#661100', '#CC6677',            '#882255', '#AA4499' ]);
wedges_color_table.append([ '#332288', '#6699CC', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#661100', '#CC6677',            '#882255',  '#AA4499' ]);
wedges_color_table.append([ '#332288', '#6699CC', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#661100', '#CC6677', '#AA4466', '#882255',  '#AA4499' ]);
wedges_color_table.append([ '#332288', '#6699CC', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#661100', '#CC6677', '#AA4466', '#882255',  '#AA4499', '#B7704A' ]);
wedges_color_table.append([ '#332288', '#6699CC', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#661100', '#CC6677', '#AA4466', '#882255',  '#AA4499', '#B7704A', '#F47621' ]);
wedges_color_table.append([ '#332288', '#6699CC', '#88CCEE', '#44AA99', '#117733', '#999933', '#DDCC77', '#661100', '#CC6677', '#AA4466', '#882255',  '#AA4499', '#B7704A', '#F47621', '#E35bE5' ]);




def gray_to_moreland_heatmap_html(gray, theme_set):
    'find the html color for a [0,1]-range value'
    if gray < 0 or gray > 1:
        raise ValueError
    if 'moreland_light' in theme_set:
        return moreland_light_heatmap[int(gray*256)]
    return moreland_heatmap[int(gray*256)]

def byte_to_hex(c):
    tmp = hex(c)[2:]
    if len(tmp) == 1:
        tmp = '0' + tmp   # left pad with '0'
    return tmp

def rgb_to_html_color(r,g,b):
    r = int(255*r)
    g = int(255*g)
    b = int(255*b)
    color = '#' + byte_to_hex(r) + byte_to_hex(g) + byte_to_hex(b)
    return color

def gray_to_html_color(gray, theme_set):
    # return rgb_to_html_color(gray, gray, gray)  ### grayscale
    return gray_to_moreland_heatmap_html(gray, theme_set)

def lat_long_map_to_boundary_xys(lat, lng, bmap, precision):
    half = precision / 2.0
    p0x,p0y = bmap(lng-half, lat-half)
    p1x,p1y = bmap(lng+half, lat-half)
    p2x,p2y = bmap(lng+half, lat+half)
    p3x,p3y = bmap(lng-half, lat+half)
    return [(p0x,p0y), (p1x,p1y), (p2x,p2y), (p3x,p3y)]

def setup_colors(theme_set):
    global colors
    colors = {
        'land': '#B0F59C',
        'sea': '#BFD1EB',
        'state_name_color': '#BFD1EB',
        'coast_border': '#6D5F47',
        'country_border': '#8D7F67',
        'sun': '#ffe000',
	'subplot_bg': '#e0e0e0', 
	'subplot_bg2': '#ffffff', 
        'subplot_dim_line': '#909090',
        'subplot_highlight': '#ffff00',
        'size_face': '#b0b0b0',
        'size_edge': '#000000',
    }
    if 'dark' in theme_set:
        colors.update({'land': '#263926', 'sea': '#596884', 
                       'state_name_color': '#7988a4',
                       'coast_border': '#1b251b', 'country_border': '#0b150b',
                       'subplot_bg': '#262626', 
                       'subplot_bg2': '#3b3b3b', 
                       'subplot_dim_line': '#686868'})
    if 'darker' in theme_set:
        colors.update({'land': '#224822', 'sea': '#293854', 
                       'coast_border': '#1b251b', 'country_border': '#0b150b',
                       'subplot_bg': '#262626', 
                       'subplot_bg2': '#3b3b3b', 
                       'subplot_dim_line': '#686868'})
    if 'light' in theme_set:
        colors.update({'land': '#f0fff0', 'sea': '#e0f8ff', 
                       'coast_border': '#909090', 'country_border': '#a0a0a0'})
    if 'lighter' in theme_set:
        colors.update({'land': '#ffffff', 'sea': '#f0f0ff', 
                       'coast_border': '#909090', 'country_border': '#a0a0a0'})

def radius_value_to_real_radius(radius_base, radius_min, radius_max, scale):
    return math.sqrt(radius_base) * scale

def setup_basemap(theme_set):
    global colors

    # lon_0 is central longitude of robinson projection.
    # resolution = 'c' means use crude resolution coastlines.
    # laea = Lambert Azimuthal Equal Area
    if 'eu_zoom' in theme_set:
        map = Basemap(projection='laea', lon_0=25, lat_0=45, llcrnrlon=-8.0, llcrnrlat=30, urcrnrlon=77, urcrnrlat=58.0, resolution='c')
        # map = Basemap(projection='robin', lon_0=0, lat_0=0, llcrnrlon=-40.0, llcrnrlat=30, urcrnrlon=80, urcrnrlat=76.0, resolution='c')
    elif 'tx_zoom' in theme_set:
        # houston: 29.762778, -95.383056, and about 800km height
        map = Basemap(projection='laea', lon_0=-96, lat_0=28.5, width=1600000, height=800000, resolution='i')
    else:
        map = Basemap(projection='robin', lon_0=0, resolution='c')
    # set a background colour
    map.drawmapboundary(fill_color = colors['sea'])  # sea color

    # draw coastlines, country boundaries, fill continents.
    # plot continents as light green, to differ from low coverage blocks
    map.fillcontinents(color = colors['land'], lake_color = colors['sea']) # continent color
    map.drawcoastlines(color = colors['coast_border'], linewidth=.4)
    if 'drawrivers' in theme_set:
        map.drawrivers(color = colors['sea'], linewidth=.4)
    country_linewidth = 0.4
    if 'drawstates' in theme_set:
        map.drawstates(color = colors['country_border'], linewidth=.4)
        country_linewidth = 1.2
    map.drawcountries(color = colors['country_border'], linewidth=country_linewidth)

    # draw lat/lon grid lines every $precision degrees.
    # |Mar 23, 2012| turned off lat/lon grid lines, per john's request
    #map.drawmeridians(np.arange(-180, 180, precision), color='#bbbbbb', dashes=[1,0])
    #map.drawparallels(np.arange(-90, 90, precision), color='#bbbbbb', dashes=[1,0])

    return map

def autorange_values(values, user_value_min, user_value_max, include_default, theme_set):
    if user_value_min is not None and user_value_max is not None:
        # skip the computation if we can
        return user_value_min, user_value_max
    new_value_min = new_value_max = None
    for (lat, lng, value) in values:
        if lat == 1.0 and lng == 1.0 and not include_default:
            # print "skipping default"
            continue
        if new_value_min is None:
            new_value_min = new_value_max = value
        else:
            if value < new_value_min:
                new_value_min = value
            if value > new_value_max:
                new_value_max = value
    if user_value_min is not None:
        new_value_min = user_value_min
    if user_value_max is not None:
        new_value_max = user_value_max
    # print "autorange: " + str(new_value_min) + " to " + str(new_value_max) + " on len " + str(len(values))
    return new_value_min, new_value_max

def value_to_html_color_and_inverse(value, value_min, value_range, invert_colormap):
    if invert_colormap:
        gray = 1 - (value - value_min) / value_range
    else:
        gray = (value - value_min) / value_range
    # xxx: could do gamma scaling here
    if gray < 0.0:
        gray = 0.0
    if gray > 1.0:
        gray = 1.0
    # print "(" + str(lat) + "," + str(lng) + "): " + str(gray)
    cc = gray_to_html_color(gray, theme_set)
    if True:
        edgecolor = "#ffffff"
    elif gray < .5:
        edgecolor = "#ffffff"
    else:
        edgecolor = "#000000"
    return (cc, edgecolor)

def value_to_html_color(value, value_min, value_range, invert_colormap):
    (cc, edgecolor) = value_to_html_color_and_inverse(value, value_min, value_range, invert_colormap)
    return cc


def wedge_i_to_html_color(i):
    return wedges_color_table[n_wedges_list][i]

def plot_circles(plt, bmap, lat_long_values, radius_values, precision, value_min, value_max, radius_min, radius_max, radius_scale, invert_colormap):
    value_range = value_max - value_min
    main_ax = plt.gca()
    for i in range(0, len(lat_long_values)):
        (lat, lng, value) = lat_long_values[i]
        (lat2, lng2, radius_value) = radius_values[i]
        center_point = bmap(lng, lat)
        cc = value_to_html_color(value, value_min, value_range, invert_colormap)
        size = radius_value_to_real_radius(radius_value, radius_min, radius_max, radius_scale)
        main_ax.add_patch(Circle(center_point, radius=size, fill=True, facecolor=cc, edgecolor=cc, linewidth=0.05, alpha=.75))
    return main_ax

def plot_wedges(plt, bmap, lat_long_values, radius_values, precision, value_min, value_max, radius_min, radius_max, radius_scale, invert_colormap):
    value_range = value_max - value_min
    main_ax = plt.gca()
    for i in range(0, len(lat_long_values)):
        (lat, lng, value) = lat_long_values[i]
        (lat2, lng2, radius_value) = radius_values[i]
        wedges_list = value.split(',')
        global n_wedges_list
        if n_wedges_list is None:
           n_wedges_list = len(wedges_list)
           max_n_wedges = len(wedges_color_table[len(wedges_color_table)-1])
           if n_wedges_list >= max_n_wedges:
               raise ValueError('too many wedges: have ' + str(n_wedges_list) + ' but code only supports ' + str(max_n_wedges))
        else:
           if n_wedges_list < len(wedges_list):
               raise ValueError('expected ' + str(n_wedges_list) + ' wedges, but see only ' + str(len(wedges_list)) + ' in ' + value)
           else:
               while n_wedges_list > len(wedges_list):
                   wedges_list.append(0.0)

        center_point = bmap(lng, lat)
        size = radius_value_to_real_radius(radius_value, radius_min, radius_max, radius_scale)

        all_wedges = 0.0
        for w in wedges_list:
            if w != '':
                all_wedges += float(w)
        if all_wedges == 0:
            continue

        cur_angle_deg = 0
        i = 0
        for w in wedges_list:
            if w == '':
                continue
            cc = wedge_i_to_html_color(i)
            #i += 1
            #if float(w) == 0.0:
            #    continue
            new_angle_deg = cur_angle_deg + (360.0 * float(w)) / all_wedges
            main_ax.add_patch(Wedge(center_point, size, cur_angle_deg, new_angle_deg, None, fill=True, facecolor=cc, edgecolor=cc, linewidth=0.05, alpha=.75))
            cur_angle_deg = new_angle_deg
            i += 1

    return main_ax

def plot_lat_long_values(plt, bmap, lat_long_values, precision, value_min, value_max, invert_colormap, radius_values):
    value_range = value_max - value_min
    main_ax = plt.gca()
    for (lat, lng, value) in lat_long_values:
        boundary_points = lat_long_map_to_boundary_xys(lat, lng, bmap, precision)
        cc = value_to_html_color(value, value_min, value_range, invert_colormap)
        main_ax.add_patch(Polygon(boundary_points, fill=True, facecolor=cc, edgecolor=cc, linewidth=0.05))
    return main_ax


def round_i_to_dt(round_0_dt, rounds):
    round_i_dt = datetime.fromtimestamp(int(round_0_dt.strftime('%s')) + rounds * 11*60)
    return round_i_dt

def plot_time_of_day(plt, bmap, fig, main_ax, rounds, round_0_dt, theme_set):
    if rounds is None:
        return
    time_of_day_in_deg = (360 * rounds * 11 / (24*60)) % 360
    # print "todd: " + str(time_of_day_in_deg)
    if time_of_day_in_deg is None:
        return
    # boundary_points = lat_long_map_to_boundary_xys(0, time_of_day_in_deg, bmap, 50)
    # print "bdy: "+str(boundary_points)
    # plt.gca().add_patch(Polygon(boundary_points, fill=True, facecolor=SUN_COLOR, edgecolor=SUN_COLOR, linewidth=0.05))
    round_i_dt = round_i_to_dt(round_0_dt, rounds)
    CS = bmap.nightshade(round_i_dt, alpha=0.1, zorder=1)
    rounds_leader = ''
    if 'tx_zoom' in theme_set:
        if not 'omitsubtitle' in theme_set:
            rounds_leader = "Effects of Hurricane Harvey on the Internet's Edge      " + rounds_leader
    rounds_appendix = ''
    if rounds is not None:
        if not 'tx_zoom' in theme_set:
            rounds_appendix += ', '
        rounds_appendix += 'round ' + str(rounds)
    if ('percent' in theme_set or 'fraction' in theme_set) and not ('eu_zoom' in theme_set or 'tx_zoom' in theme_set):
        left_long_lat = (-160, 85)
        right_long_lat = (160,85)
        title_font_size = 'x-large'
        hacky_raise = 500000
	# draw title by hand
        (left_x, left_y) = bmap(left_long_lat[0], left_long_lat[1])
        plt.text(left_x, left_y + hacky_raise, round_i_dt.strftime("%Y-%m-%dt%H:%M (UTC)"), ha='left', va='baseline', fontsize=title_font_size, zorder=12)
        (right_x, right_y) = bmap(right_long_lat[0], right_long_lat[1])
        plt.text(right_x, right_y + hacky_raise, 'r' + str(rounds), ha='right', va='baseline', fontsize=title_font_size, zorder=12)
    elif 'tx_zoom' in theme_set:
        if 'omitsubtitle' in theme_set:
            main_ax.text(0.5, 1.02, 'Network Blocks Observed in August 2017', horizontalalignment='center', verticalalignment='bottom', transform=main_ax.transAxes, fontsize=20)
        else:
            main_ax.text(0, 1.02, rounds_leader, horizontalalignment='left', verticalalignment='bottom', transform=main_ax.transAxes, fontsize=20)
            local_round_i_dt = round_i_dt + timedelta(hours=-6)
            main_ax.text(1, 1.02, round_i_dt.strftime("%Y-%m-%dt%H:%M UTC") + "\n" + local_round_i_dt.strftime("%Y-%m-%dt%H:%M CDT") + "\n" + rounds_appendix, horizontalalignment='right', verticalalignment='bottom', transform=main_ax.transAxes, fontsize=12)
            main_ax.text(0.95, 0.98, 'Mississippi', color=colors['state_name_color'], size=10, horizontalalignment='right', verticalalignment='top', transform=main_ax.transAxes)
            main_ax.text(0.72, 0.98, 'Louisiana', color=colors['state_name_color'], size=10, horizontalalignment='right', verticalalignment='top', transform=main_ax.transAxes)
            main_ax.text(0.6, 0.98, 'Texas', color=colors['state_name_color'], size=10, horizontalalignment='right', verticalalignment='top', transform=main_ax.transAxes)
            main_ax.text(0.35, 0.02, 'Mexico', color=colors['state_name_color'], size=10, horizontalalignment='right', verticalalignment='bottom', transform=main_ax.transAxes)
    else:
        fig.suptitle(rounds_leader + round_i_dt.strftime("%Y-%m-%dt%H:%M (UTC)") + rounds_appendix)

def scale_1k_text(value):
    if value < 1000:
        return str(int(value))
    else:
        return "{:d}k".format(int(value/1000))

def plot_legend(fig, value_min, value_max, invert_colormap, legend_title, legend_url, theme_set, dataset_name, legend_number, legend_copyright):
    # following for coloring legend
    #"""
    global colors

    legend_x_shift = 0
    if legend_number == 1:
        legend_x_shift = -0.020
    elif legend_number == 2:
        legend_x_shift = +0.025
        raise ValueError('plot_legend no longer works with 2nd legend')
    left_bottom_width_height = [0.05 + legend_x_shift, 0.25, 0.02, 0.5]
    ax1 = fig.add_axes(left_bottom_width_height, zorder=0)
    step_height = 1.0/14.0

    ax1.set_axis_off()
    ax1.text(0.75, 1.05, legend_title, horizontalalignment='center')

    value_range = value_max - value_min
    for td_i in range(0,14):  # 14 "blocks" in the legend
        i = 14 - 1 - td_i
        facecolor = edgecolor = "#ffffff"
        value = None
        value_text = None
        horiz_offset = 1.05
        horiz_align = 'left'
        if i == 11:
            continue
        elif i == 12:
            value_text = 'land (no networks)'
            facecolor = colors['land']
        elif i == 13:
            value_text = 'ocean (no networks)'
            facecolor = colors['sea']
        else:
            value = (10.0-i) / 10.0 * value_range + value_min
            horiz_offset = 2.5
            horiz_align = 'right'
            percent_value = value
            if 'fraction' in theme_set:
                percent_value = value * 100
            if ('percent' in theme_set or 'fraction' in theme_set):
                format_string = "{:.0f}%"
                if value_min < 0:
                    format_string = "{:+.0f}%"
                value_text = format_string.format(percent_value)
                if percent_value == 0:
                    value_text = "0%"
            elif 'scale1k' in theme_set:
                value_text = scale_1k_text(value)
            elif 'scale500' in theme_set:
                if value < 500:
                    value_text = str(int(value))
                elif value >= 5000:
                    value_text = '>5000'
                else:
                    value_text = "{:d}".format(int(value/500)*500)
            else:
                value_text = str(value)
            (facecolor, edgecolor) = value_to_html_color_and_inverse(value, value_min, value_range, invert_colormap)
        ax1.add_patch(Rectangle((0,step_height*td_i), 1, step_height, fill=True, facecolor=facecolor, edgecolor=edgecolor))
        #ax1.text(1.1, (td_i+0.3)*step_height, value_text, fontproperties=FontProperties(size=8, horizontalalignment='right'))
        ax1.text(horiz_offset, (td_i+0.3)*step_height, value_text, fontsize=8, horizontalalignment=horiz_align)

    x = -1.1
    text_step = -.75*step_height
    y = 1.75*text_step
    ax1.text(x, y, legend_url, fontsize='x-small')
    y += text_step
    if dataset_name is not None:
        ax1.text(x, y, dataset_name, fontsize='x-small')
        y += text_step
    ax1.text(x, y, legend_copyright, fontsize='x-small', bbox={'facecolor':'white', 'alpha':0.8, 'pad':2, 'edgecolor':'none'})
    y += text_step


def plot_wedges_legend(fig, invert_colormap, legend_title, legend_url, theme_set, dataset_name, legend_number, legend_copyright):
    # following for coloring legend
    #"""
    global colors

    legend_x_shift = 0
    if legend_number == 1:
        legend_x_shift = -0.020
    elif legend_number == 2:
        legend_x_shift = +0.025
        raise ValueError('plot_legend no longer works with 2nd legend')
    left_bottom_width_height = [0.05 + legend_x_shift, 0.25, 0.02, 0.5]
    ax1 = fig.add_axes(left_bottom_width_height, zorder=0)
    step_height = 1.0/14.0

    # pick names out of the title
    if legend_title.find(":") >= 0:
        (real_legend_title, all_site_titles) = legend_title.split(":")
    else:
        (real_legend_title, all_site_titles) = (legend_title, "site")
    if all_site_titles.find(",") >= 0:
        site_titles = all_site_titles.split(",")
    else:
        site_titles = all_site_titles

    ax1.set_axis_off()
    ax1.text(0.75, 1.05, real_legend_title, horizontalalignment='center')

    for td_i in range(0,n_wedges_list):
        i = n_wedges_list - 1 - td_i
        facecolor = edgecolor = "#ffffff"
        value = None
        value_text = None
        horiz_offset = 1.05
        horiz_align = 'left'

        if td_i >= len(site_titles):
            continue

        horiz_offset = 2.5
        horiz_align = 'right'
        value_text = site_titles[td_i]
        wedgecolor = wedge_i_to_html_color(td_i)

        ax1.add_patch(Rectangle((0,step_height*(13-td_i)), 1, step_height, fill=True, facecolor=wedgecolor, edgecolor=edgecolor))
        ax1.text(horiz_offset, ((13-td_i)+0.3)*step_height, value_text, fontsize=8, horizontalalignment=horiz_align)

    x = -1.1
    text_step = -.75*step_height
    y = 1.75*text_step
    ax1.text(x, y, legend_url, fontsize='x-small')
    y += text_step
    if dataset_name is not None:
        ax1.text(x, y, dataset_name, fontsize='x-small')
        y += text_step
    ax1.text(x, y, legend_copyright, fontsize='x-small')
    y += text_step


def plot_circles_legend(plt, bmap, main_ax, value_min, value_max, radius_scale, legend_title, theme_set):
    global colors

    value_range = value_max - value_min
    # print "from " + str(value_min) + " to " + str(value_max)
    base_lng = -179.9
    base_lat = -30
    hacky_increment = 1000000  # empirically determined :-(
    if 'tx_zoom' in theme_set:
        base_lng = -103
        base_lat = 27
        hacky_increment = 47000
        # with --radius-max=250
    (base_x, bad_y) = bmap(base_lng, 0)
    base_x += 0.5*hacky_increment
    (bad_x, base_y) = bmap(base_lng, base_lat)
    for i in range(0,10):
        facecolor = colors['size_face']
        edgecolor = colors['size_edge']
        base_value = (i / 10.0) * value_range + value_min
        value_text = scale_1k_text(base_value)
        if i == 9:
            value_text += "+"
        value = radius_value_to_real_radius(base_value, value_min, value_max, radius_scale)

        y = base_y + hacky_increment * i  # from hacky_raise in plot_time_of_day :-(
        main_ax.add_patch(Circle((base_x, y), radius=value, fill=True, facecolor=facecolor, edgecolor=edgecolor, clip_on=False))
        # print "legend from " + str(base_value) + " to " + str(value) + " at " + str((x,y))
        main_ax.text(base_x - hacky_increment*.6, y, value_text, fontsize=8, horizontalalignment='right')
    main_ax.text(base_x - hacky_increment*.5, base_y + hacky_increment*10, legend_title, horizontalalignment='center', bbox={'facecolor':'white', 'alpha':0.8, 'pad':2, 'edgecolor':'none'})


def read_subplot_data(filename):
    data_f = open(filename, "r")
    round = 0
    values = []
    for line in data_f:
        line = line.rstrip()
        if line.startswith('#fsdb'):
            expected_schema = '#fsdb -F t rounded_lat_long round addr_count_rpct'
            if line != expected_schema:
                raise ValueError('input file format has odd schema: ' + line + "\n\tnot " + expected_schema)
        if line.startswith('#'):
            continue
        f = line.strip().split('\t')
        if int(f[1]) != round:
            raise ValueError('input file problem: expected round ' + str(round) + ' but got ' + f[1])
        values.append(float(f[2]))
        round += 1
    return values

def round_to_matplotlib_date(rounds, round_0_dt):
    return int(round_0_dt.strftime('%s')) + rounds_to_seconds(rounds)

def plot_subplots(plt, bmap, fig, main_ax, value_min, value_max, invert_colormap, round_0_dt, rounds, precision):
    """
a complete hack for the video, zoom in on Warsaw and Minsk.
assumes magic files a12w_rounds_53,21_rpct.fsdb and a12w_rounds_53,27_rpct.fsdb
exist with schema rounded_lat_long round addr_count_rpct

hardcoded: look at warsaw (53,21) and minsk (53,27) (long,lat) for rounds 350 to 550
    """
    FIRST_ROUND=350 # 2013-04-27t16:10 UTC
    LAST_ROUND=550  # 2013-04-29t04:50 UTC
    # sunrise and sunset times are for the given lat long
    # computed from http://aa.usno.navy.mil/data/docs/RS_OneDay.php.
    value_range = value_max - value_min
    global colors
    tick_locs = []
    cur_tick_loc_dt = datetime.strptime("2013-04-27t18:00", "%Y-%m-%dt%H:%M")
    for i in range(0,10):
        tick_locs.append(mdates.date2num(cur_tick_loc_dt))
        cur_tick_loc_dt = datetime.fromtimestamp(int(cur_tick_loc_dt.strftime('%s')) + 6*60*60)
        
    for details in [{'title': 'Minsk',
                     'ax_left': 0.70,
                     'filename': "a12w_rounds_53,27_rpct.fsdb",
                     'lat': 53.0,
                     'long': 27.0, 
                     'link_left': True, 
                     'sunrise': ['2013-04-27t01:53', '2013-04-28t01:52', '2013-04-29t01:51' ],
                     'sunset': ['2013-04-27t14:59', '2013-04-28t14:59', '2013-04-29t15:00' ] },
                    {'title': 'Warsaw', 
                     'ax_left': 0.05, 
                     'filename': "a12w_rounds_53,21_rpct.fsdb",
                     'lat': 53.0,
                     'long': 21.0,
                     'link_left': False, 
                     'sunrise': ['2013-04-27t02:00', '2013-04-28t02:00', '2013-04-29t01:59' ],
                     'sunset': ['2013-04-27t14:51', '2013-04-28t14:52', '2013-04-29t14:52' ] } ]:
        # hack: a 2nd plot just to clear the background.  should probably bejust a rectangle
        BG_WD = 0.05
        BG_HT = 0.2
        ax_bg = fig.add_axes([details['ax_left']-BG_WD, 0.25-BG_HT, 0.25+BG_WD*1.5, 0.5+BG_HT*2], axisbg='white')
        for spine in ax_bg.spines.itervalues():
            spine.set_visible(False)
        ax_bg.axes.get_xaxis().set_visible(False)
        ax_bg.axes.get_yaxis().set_visible(False)
        #
        # the actual plot
        #
        data = read_subplot_data(details['filename'])
        ax = fig.add_axes([details['ax_left'], 0.25, 0.25, 0.5], axisbg=colors['subplot_bg'])
        ax.set_xlabel('time')
        ax.set_ylabel('change from mean')
        ax.set_ylim(value_min, value_min + value_range)
        ax.set_xlim(mdates.date2num(round_i_to_dt(round_0_dt, FIRST_ROUND)), mdates.date2num(round_i_to_dt(round_0_dt, LAST_ROUND)))
        ax.format_xdata = mdates.DateFormatter('%s') # , 'UTC')
        ax.xaxis.set_major_formatter(mdates.DateFormatter('%H:%MZ')) # , 'UTC'))
        ax.xaxis.set_major_locator(matplotlib.ticker.FixedLocator(tick_locs))
        plt.setp(ax.xaxis.get_majorticklabels(), rotation=45)
        ax_top = ax.twiny()
        ax_top.set_xlabel('round')
        ax_top.set_xlim(FIRST_ROUND, LAST_ROUND)
        ax_top.set_ylim(value_min, value_min + value_range)
        # drop in sunrise/sunset
        for i in range(0,len(details['sunrise'])):
            left_x = mdates.date2num(datetime.strptime(details['sunrise'][i], "%Y-%m-%dt%H:%M"))
            right_x = mdates.date2num(datetime.strptime(details['sunset'][i], "%Y-%m-%dt%H:%M"))
            ax.add_patch(Rectangle((left_x, value_min), right_x-left_x, value_range, color=colors['subplot_bg2'], zorder=10))
        # finally the data
        ax_top.plot(data, color=colors['subplot_dim_line'])  # #dddddd is white in our color ramp, use half of it
        # add a line on zero
        ax_top.add_patch(ConnectionPatch((FIRST_ROUND, 0), (LAST_ROUND, 0), coordsA="data", axesA=ax_top, color=colors['subplot_dim_line'], linestyle='dashed'))
        #
        # Now draw a fancy plot up to the current round
        #
        last_x = None
        last_y = None
        for r in range(FIRST_ROUND, rounds):
            x = r
            y = data[r]
            data_color = value_to_html_color(y, value_min, value_range, invert_colormap)
            if last_y is not None:
                # ax_top.plot([(last_x, last_y), (x, y)], color='#ff0000', linestyle='-', linewidth=100, zorder=2)
                ax_top.add_patch(ConnectionPatch((last_x, last_y), (x, y), coordsA="data", axesA=ax_top, color=data_color, linestyle='solid', linewidth=4, zorder=3))
            (last_x, last_y) = (x, y)
        if rounds >= FIRST_ROUND and rounds <= rounds:
            x = rounds
            y = data[rounds]
            data_color = value_to_html_color(y, value_min, value_range, invert_colormap)
            ax_top.add_patch(Ellipse((x, y), width=7, height=2, fill=1, facecolor=data_color, edgecolor='#ffffff', linewidth=0.05, zorder=3))
        #
        # finally link the subplot into the main one
        #
        boundary_points = lat_long_map_to_boundary_xys(details['lat'], details['long'], bmap, precision)
        main_ax.add_patch(Polygon(boundary_points, fill=False, edgecolor=colors['subplot_highlight'], linewidth=2, zorder=2))
        subgraph_boundary_points = [(FIRST_ROUND, value_min), (LAST_ROUND, value_min), (LAST_ROUND, value_max), (FIRST_ROUND, value_max)]
        p0 = 1
        p1 = 2
        if details['link_left']:
            p0 = 3
            p1 = 0
        main_ax.add_patch(ConnectionPatch(boundary_points[p0], subgraph_boundary_points[p0], coordsA="data", axesA=main_ax, axesB=ax_top, color=colors['subplot_highlight'], linestyle='dashed', linewidth=3, zorder=5, clip_on=False))
        main_ax.add_patch(ConnectionPatch(boundary_points[p1], subgraph_boundary_points[p1], coordsA="data", axesA=main_ax, axesB=ax_top, color=colors['subplot_highlight'], linestyle='dashed', linewidth=3, zorder=5, clip_on=False))




def parse_input(theme_set):
    '''
parse_input: check the schema

This function is a nightmare of backwards compatibility,
all because we kept changing the inputs and never had proper Fsdb
input column parsing.  Ugh.
'''
    lat_long_keys = set()
    lat_long_values = []
    radius_values = []
    for line in sys.stdin:
        if line.startswith('#fsdb'):
            stripped_line = line.strip(" \t\n")
            expected_schemas = set(['#fsdb -F t rounded_lat_long value', 
                                    '#fsdb -F t rounded_lat_long addr_count_delta', 
                                    '#fsdb -F t rounded_lat_long addr_count_rpct', 
                                    '#fsdb -F t rounded_lat_long queries_per_geobin_sum', 
                                    '#fsdb -F t rounded_lat_long out_frac out_count block_count'])
            expected_schema = ""
            if args.value_col != "2":
                expected_schema = "#fsdb -F t rounded_lat_long " + args.value_col
                if 'circles' in theme_set or 'wedges' in theme_set:
                    expected_schema += " " + args.radius_col
                expected_schemas = set([expected_schema, expected_schema + " block_count"])
            if stripped_line not in expected_schemas:
                raise ValueError("input file format has odd schema: " + stripped_line + "\n\tnot one of" + "\n\t" + "\n\t".join(expected_schemas) + "\n")
        if line.startswith('#'):
            continue
        f = line.strip().split('\t')
        if f[0] == 'bad':
            continue
        if f[0] in lat_long_keys:
            raise ValueError("duplicate lines for lat_long: " + f[0])
        else:
            lat_long_keys.add(f[0])
        lat_long = f[0].split(',')
        lat_long_float = (float(lat_long[0]), float(lat_long[1]))
        if 'wedges' in theme_set:
            lat_long_values.append((lat_long_float[0], lat_long_float[1], f[1]))
        else:
            lat_long_values.append((lat_long_float[0], lat_long_float[1], float(f[1])))
        if len(f) > 2:
            radius_values.append((lat_long_float[0], lat_long_float[1], float(f[2])))
    return (lat_long_values, radius_values)


if __name__ == '__main__':
    parser = argparse.ArgumentParser(description = "take geobin data and plot a world map", epilog="""

EXAMPLE:

Input (geobin input):

	#fsdb -F t rounded_lat_long value
	-1,-49  1
	-1,-53  1
	-1,-77  2
	-1,-79  57
	-1,11   1
	-1,117  5
	-1,31   4
	-1,37   1
	-11,-39 3
	-11,-65 1
	...

Two columns, and the second is the value that sets the scale.
We check verify the name of that column. 
Set it with --value-col=value.
(It *must* be the second column, however.)

Sample command:
    	< in.fsdb geobin_to_worldmap --theme dark

Output:
    (a pretty picture, saved as worldmap.png)

Or, when --theme=circles, we take two columns in addition to rounded_lat_long,
the value and the radius of the circle (the second and third columns).
We check both names the both columns. 
Set it with --value-col=out_frac --radius-column=out_count.

Or, when --theme=wedges, we take two columns in addition to rounded_lat_long,
the value and the radius of the circle (the second and third columns).
The value is a comma-separated list of wedges of a pie.

We check both names the both columns. 
Set it with --value-col=out_frac --radius-column=out_count.

Or sample geofraccount input:

	#fsdb -F t rounded_lat_long out_frac out_count block_count
	-13,131 1.0000  1       1
	-21,149 0.5000  1       2
	-21,55  0.0000  0       7
	-23,151 0.5000  1       2
	-25,153 0.2500  1       4
	-27,133 0.1612  54      335
	-27,151 0.0000  0       1
	-27,153 0.4634  95      205

    	< in.fsdb geobin_to_worldmap --theme dark,fraction,circles

Three columns, where the second sets the color (a linear scale) 
and the third sets the radius.

By adding the additional columns and "circles"

Other --theme options:

    drawstates (default: don't)
    drawrivers (default: don't)

    """)
    #flags.DEFINE_string('output_prefix', '', '')
    #flags.DEFINE_integer('min_blocks_to_show_outage', 100, 'don\'t show outages for locations with less than this many blocks')
    #flags.DEFINE_string('base_file', '', '')
    #flags.DEFINE_string('total_blocks_file', '', '')
    #flags.DEFINE_string('diurnal_blocks_file', '', '')
    parser.add_argument('--output', '-o', help='file to write', dest='output_file', default='worldmap')
    parser.add_argument('--format', help='format for output (png or pdf)', dest='output_format', default='png')
    parser.add_argument('--title', help='title for plot', dest='title', default=None)
    parser.add_argument('--legend-title', help='title for legend', dest='legend_title', default='')
    parser.add_argument('--precision', help='latitudue/longitude bin precision (in degrees)', dest='lat_long_precision', default=2)
    parser.add_argument('--invert-colormap', help='make low to high values run black to white (not white to black)', action='store_true', default=False)
    parser.add_argument('--value-min', help='minimum value for black in the plot', dest='value_min', type=float, default=None)
    parser.add_argument('--value-max', help='maximum value for white in the plot', dest='value_max', type=float, default=None)
    parser.add_argument('--value-col', help='name of the value column (default: the 2nd column)', dest='value_col', default="NONE")
    parser.add_argument('--value-legend', help='title of the legend of the value column (default: with circles: "out (%)")', dest='value_legend', default="DEFAULT")
    parser.add_argument('--radius-min', help='minimum value radius in a circle plot', dest='radius_min', type=float, default=None)
    parser.add_argument('--radius-max', help='maximum value radius in a circle plot', dest='radius_max', type=float, default=None)
    parser.add_argument('--radius-col', help='name of the radius column (default: the 3nd column)', dest='radius_col', default="NONE")
    parser.add_argument('--radius-scale', help='scaling factor for radius (default: 3000)', dest='radius_scale', type=float, default=3000)
    parser.add_argument('--radius-legend', help='title of the legend of the radius column (default: with circles: "size (blocks)")', dest='radius_legend', default="DEFAULT")
    parser.add_argument('--autorange-include-default', help='include (1,1) in autoranging (as the default location it can be misleading)', dest='autorange_include_default', action='store_true', default=False)
    parser.add_argument('--no-autorange-include-default', help='do not include (1,1) in autoranging (as the default location it can be misleading)', dest='autorange_include_default', action='store_false', default=False)
    parser.add_argument('--time-of-day', help='shows sun at a given time-of-day in seconds, or with "rounds", in 11-minute rounds', default=None)
    parser.add_argument('--round-0-date', help='UTC date of round 0 (ISO format: 2014-01-01)', default=None)
    parser.add_argument('--theme', help='set color theme (dark or light; \n\t\tpercent or fraction (or not); eu_zoom); 1080p (default: 720p); default or circles or wedges', default='light')
    parser.add_argument('--dataset-name', help='specify dataset name', default=None)
    parser.add_argument('--url', help='specify url in legend', default='https://ant.isi.edu/diurnal/')
    yyyy = datetime.today().strftime("%Y")
    parser.add_argument('--copyright', help='specify copyright in legend', default='Copyright (C) ' + yyyy + ' by University of Southern California')
    args = parser.parse_args()

    theme_set = set()
    if args.theme is not None:
        for key in args.theme.split(','):
            theme_set.add(key)

    # set up columns
    if args.value_col == 'NONE':
        args.value_col = "2"
    if ( 'circles' in theme_set or 'wedges' in theme_set ) and args.radius_col == 'NONE':
        args.radius_col = "3"
        if args.value_col == '2':
            raise ValueError('with circles or wedges theme, must specific both --value-col=foo  AND --radius-col=bar, or neither')

    (lat_long_values, radius_values) = parse_input(theme_set)
    if not 'wedges' in theme_set:
        value_min, value_max = autorange_values(lat_long_values, args.value_min, args.value_max, args.autorange_include_default, theme_set)
    if 'circles' in theme_set or 'wedges' in theme_set:
        radius_min, radius_max = autorange_values(radius_values, args.radius_min, args.radius_max, args.autorange_include_default, theme_set)

    # xxx: don't yet autorange radius_values

    # hit 720p: 1280x720 @ 100pi is 12.8x7.2
    # OR 1080p: 1920x1080 is 19.2x10.8 BUT instead of changing size, we change DPI
    the_figsize = (12.8,7.2)
    the_figdpi = 100
    if '1080p' in theme_set:
        # the_figsize = (19.2,10.8)
        the_figdpi = 150
    fig = plt.figure(figsize=the_figsize, dpi=the_figdpi)
    setup_colors(theme_set)
    bmap = setup_basemap(theme_set)

    if 'circles' in theme_set:
        main_ax = plot_circles(plt, bmap, lat_long_values, radius_values, args.lat_long_precision, value_min, value_max, radius_min, radius_max, args.radius_scale, args.invert_colormap)
    elif 'wedges' in theme_set:
        if args.value_max is not None:
            n_wedges_list = int(args.value_max)
        main_ax = plot_wedges(plt, bmap, lat_long_values, radius_values, args.lat_long_precision, 0, 1, radius_min, radius_max, args.radius_scale, args.invert_colormap)
    else:
        main_ax = plot_lat_long_values(plt, bmap, lat_long_values, args.lat_long_precision, value_min, value_max, args.invert_colormap, radius_values = radius_values)

    round_0_dt = None
    if args.round_0_date:
        input_date = args.round_0_date
        if len(input_date) == 10:
             input_date += "t00:00:01"
        round_0_dt = datetime.strptime(input_date, "%Y-%m-%dt%H:%M:%S")
    rounds = None
    if args.time_of_day is not None:
        if args.time_of_day[-6:] == 'rounds':
            rounds = int(args.time_of_day[0:-6])
        else:
            raise ValueError('--time-of-day must be expressed in rounds currently')
    plot_time_of_day(plt, bmap, fig, main_ax, rounds, round_0_dt, theme_set)
    if args.radius_legend == 'DEFAULT':
        args.radius_legend = "size (blocks)"
    if 'subplots' in theme_set:
        plot_subplots(plt, bmap, fig, main_ax, value_min, value_max, args.invert_colormap, round_0_dt, rounds, args.lat_long_precision)
    elif 'circles' in theme_set:
        if args.value_legend == 'DEFAULT':
            args.value_legend = "out (%)"
        if args.value_legend != 'OMIT':
            plot_legend(fig, value_min, value_max, args.invert_colormap, args.value_legend, args.url, theme_set, args.dataset_name, 1, args.copyright)
        plot_circles_legend(plt, bmap, main_ax, radius_min, radius_max, args.radius_scale, args.radius_legend, theme_set)
    elif 'wedges' in theme_set:
        if args.value_legend == 'DEFAULT':
            args.value_legend = "site"
        plot_wedges_legend(fig, args.invert_colormap, args.value_legend, args.url, theme_set, args.dataset_name, 1, args.copyright)
        plot_circles_legend(plt, bmap, main_ax, radius_min, radius_max, args.radius_scale, args.radius_legend, theme_set)
    else:
        if args.value_legend == 'DEFAULT':
            args.value_legend = args.dataset_name
        if args.value_legtend != 'OMIT':
            plot_legend(fig, value_min, value_max, args.invert_colormap, args.legend_title, args.url, theme_set, args.value_legend, 0, args.copyright)

    if args.title is not None and not ( 'tx_zoom' in theme_set):
        plt.title(args.title)

    output_file = args.output_file
    if output_file[-len(args.output_format):] != args.output_format:
        output_file = output_file + "." + args.output_format
    plt.savefig(output_file, format=args.output_format)


