Generate interactive data exploration tool

    Output is an html page, rendered to 'eurominder_explorer.html' in the output directory.

    Data structure (only relevant items are shown below):

    .. code-block:: none

        {
            "nuts2Features": [{"geometry": [<boundary geometry>],
                               "properties": { "NUTS_ID": "AT11" } },
                              {"geometry": [<boundary geometry>],
                               "properties": { "NUTS_ID": "AT12" } },
                              ...],

            "regionNames": {"AT11": "Burgenland",
                            "AT13": "Wien",
                            ...
                           },

            "indicatorDescriptions": {"000005": "some description",
                                      "000010": "another description",
                                      ...},

            "indicators": [ {"key": "AT11",
                             "000005 2010": 37.0
                             "000005 2011": 38.0
                             ...
                             "000010 2010": 1234.0
                             "000010 2011": 1235.0
                             ...
                             "Mean temperature (C) Apr-Sep": 16.0,
                             "Mean temperature (C) Oct-Mar": 9.0,
                             ...
                             },
                            {"key": "AT12",
                             "000005 2010: 19.0,
                             ..
                            },
                            ...
                         ]

            "ranges": {"000005": [10, 20],
                       "000010": [1234, 5678],
                       ...,
                       "Mean temperature (C) Apr-Sep": 16.0,
                       "Mean temperature (C) Oct-Mar": 9.0,
                       ...
                       },

            "years": ["1999", "2000", ...],

        }

def eurominder_explorer():
    """Generate interactive data exploration tool

    Output is an html page, rendered to 'eurominder_explorer.html' in the output directory.

    Data structure (only relevant items are shown below):

    .. code-block:: none

        {
            "nuts2Features": [{"geometry": [<boundary geometry>],
                               "properties": { "NUTS_ID": "AT11" } },
                              {"geometry": [<boundary geometry>],
                               "properties": { "NUTS_ID": "AT12" } },
                              ...],

            "regionNames": {"AT11": "Burgenland",
                            "AT13": "Wien",
                            ...
                           },

            "indicatorDescriptions": {"000005": "some description",
                                      "000010": "another description",
                                      ...},

            "indicators": [ {"key": "AT11",
                             "000005 2010": 37.0
                             "000005 2011": 38.0
                             ...
                             "000010 2010": 1234.0
                             "000010 2011": 1235.0
                             ...
                             "Mean temperature (C) Apr-Sep": 16.0,
                             "Mean temperature (C) Oct-Mar": 9.0,
                             ...
                             },
                            {"key": "AT12",
                             "000005 2010: 19.0,
                             ..
                            },
                            ...
                         ]

            "ranges": {"000005": [10, 20],
                       "000010": [1234, 5678],
                       ...,
                       "Mean temperature (C) Apr-Sep": 16.0,
                       "Mean temperature (C) Oct-Mar": 9.0,
                       ...
                       },

            "years": ["1999", "2000", ...],

        }
    """
    # switch off some pandas warnings
    import pandas as pd
    pd.options.mode.chained_assignment = None

    # page template
    template = jenv.get_template("eurominder_explorer.html")

    # container for template context
    context = dict()

    # plot data
    data = dict()

    # a database client/session to run queries in
    cl = client.get_client()
    session = cl.create_session()

    #
    # query data
    #

    # get region names and keys
    query = session.query(models.NUTS2Region.key,
                          models.NUTS2Region.name).all()
    data['regionNames'] = {t[0]: t[1] for t in query}

    # get indicator descriptions
    query = session.query(models.EuroStatIndicator.number,
                          models.EuroStatIndicator.description)
    data['indicatorDescriptions'] = {t[0]: t[1] for t in query}

    # build options for HTML selector for selecting which indicator to show
    context['y_field_selector_options'] = ''
    context['x_field_selector_options'] = ''
    for i_field, number in enumerate(sorted(data['indicatorDescriptions'].keys())):
        description = data['indicatorDescriptions'][number]

        # first field is default option for y selector, second field for x selector
        y_selected = " selected=\"selected\"" if i_field == 0 else ""
        x_selected = " selected=\"selected\"" if i_field == 1 else ""

        context['y_field_selector_options'] += "<option value=\"{:s}\" {:s}>{:s}</option>" \
                                               "".format(number, y_selected, description)
        context['x_field_selector_options'] += "<option value=\"{:s}\" {:s}>{:s}</option>" \
                                               "".format(number, x_selected, description)

    # get all eurostats data
    query = session.query(models.EuroStatValue.value,
                          models.EuroStatValue.year,
                          models.EuroStatIndicator.number,
                          models.NUTS2Region.key) \
        .join(models.EuroStatIndicator) \
        .join(models.NUTS2Region)
    eurostat = cl.df_query(query)

    # reformat to list of value dicts per region, as described above
    data['indicators'] = []
    for region_key, region_data in eurostat.groupby('key'):
        region_data['str_id'] = region_data['number'] + ' ' + region_data['year'].apply(lambda x: str(x))
        value_dict = region_data.set_index('str_id')['value'].to_dict()
        value_dict['key'] = region_key
        data['indicators'].append(value_dict)

    # store min/max values per indicator across all years, to keep plot scales fixed on playback
    data['ranges'] = dict()
    for number, indicator_data in eurostat.groupby('number'):
        data['ranges'][number] = [indicator_data['value'].min(), indicator_data['value'].max()]

    # build options for year selector (also store options in a list, for playback control)
    data['years'] = sorted([str(y) for y in eurostat['year'].unique()])
    context['year_selector_options'] = ''
    for year in data['years']:
        year_selected = " selected=\"selected\"" if year == "2010" else ""
        context['year_selector_options'] += "<option {:s}>{:s}</option>".format(year_selected, year)

    # query climate data
    query = session.query(models.ClimateValue.value,
                          models.ClimateIndicator.description,
                          models.NUTS2Region.key) \
        .join(models.ClimateIndicator) \
        .join(models.NUTS2Region)
    climate = cl.df_query(query).set_index('key')

    # inject data into 'indicators' records
    for record in data['indicators']:
        try:
            # index by a list of keys to be sure to get back a DataFrame even for one matching record
            cdata = climate.loc[[record['key']]]
        except KeyError:
            # no data available
            continue

        for description, value in cdata.set_index('description')['value'].iteritems():
            record[description] = value

    # sorted list of climate descriptions
    data['climateDescriptions'] = sorted(list(climate['description'].unique()))

    # store ranges of climate indicators, lump summer and winter together (by stripping season indicator)
    # to get comparable scales
    climate['variable_description'] = climate['description'].map(lambda s: ' '.join(s.split()[:-1]))
    for variable_description, indicator_data in climate.groupby('variable_description'):
        r = [indicator_data['value'].min(), indicator_data['value'].max()]
        data['ranges'][variable_description + pipeline.CLIMATE_SEASON_SUFFIXES['winter']] = r
        data['ranges'][variable_description + pipeline.CLIMATE_SEASON_SUFFIXES['summer']] = r

    # inject GeoJSON polygons
    with open(pipeline.NUTS2GeoJSONInputFile().input_file) as in_file:
        nuts2_boundaries = json.load(in_file)
    data['nuts2Features'] = nuts2_boundaries['features']

    #
    # render template
    #

    # inject data
    context['data'] = json.dumps(data, indent=4)

    out_file = path.join(out_dir, "eurominder_explorer.html")
    html_content = template.render(**context)
    with open(out_file, 'w') as f:
        f.write(html_content)

    # done, clean up
    session.close()