Esempio n. 1
0
    def geo_json(self, geo_path=None, data_out='data.json', data=None,
                 columns=None, key_on=None, threshold_scale=None,
                 fill_color='blue', fill_opacity=0.6, line_color='black',
                 line_weight=1, line_opacity=1, legend_name=None,
                 topojson=None, reset=False):
        '''Apply a GeoJSON overlay to the map.

        Plot a GeoJSON overlay on the base map. There is no requirement
        to bind data (passing just a GeoJSON plots a single-color overlay),
        but there is a data binding option to map your columnar data to
        different feature objects with a color scale.

        If data is passed as a Pandas dataframe, the "columns" and "key-on"
        keywords must be included, the first to indicate which DataFrame
        columns to use, the second to indicate the layer in the GeoJSON
        on which to key the data. The 'columns' keyword does not need to be
        passed for a Pandas series.

        Colors are generated from color brewer (http://colorbrewer2.org/)
        sequential palettes on a D3 threshold scale. The scale defaults to the
        following quantiles: [0, 0.5, 0.75, 0.85, 0.9]. A custom scale can be
        passed to `threshold_scale` of length <=6, in order to match the
        color brewer range.

        TopoJSONs can be passed as "geo_path", but the "topojson" keyword must
        also be passed with the reference to the topojson objects to convert.
        See the topojson.feature method in the TopoJSON API reference:
        https://github.com/mbostock/topojson/wiki/API-Reference


        Parameters
        ----------
        geo_path: string, default None
            URL or File path to your GeoJSON data
        data_out: string, default 'data.json'
            Path to write Pandas DataFrame/Series to JSON if binding data
        data: Pandas DataFrame or Series, default None
            Data to bind to the GeoJSON.
        columns: dict or tuple, default None
            If the data is a Pandas DataFrame, the columns of data to be bound.
            Must pass column 1 as the key, and column 2 the values.
        key_on: string, default None
            Variable in the GeoJSON file to bind the data to. Must always
            start with 'feature' and be in JavaScript objection notation.
            Ex: 'feature.id' or 'feature.properties.statename'.
        threshold_scale: list, default None
            Data range for D3 threshold scale. Defaults to the following range
            of quantiles: [0, 0.5, 0.75, 0.85, 0.9], rounded to the nearest
            order-of-magnitude integer. Ex: 270 rounds to 200, 5600 to 6000.
        fill_color: string, default 'blue'
            Area fill color. Can pass a hex code, color name, or if you are
            binding data, one of the following color brewer palettes:
            'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu',
            'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'.
        fill_opacity: float, default 0.6
            Area fill opacity, range 0-1.
        line_color: string, default 'black'
            GeoJSON geopath line color.
        line_weight: int, default 1
            GeoJSON geopath line weight.
        line_opacity: float, default 1
            GeoJSON geopath line opacity, range 0-1.
        legend_name: string, default None
            Title for data legend. If not passed, defaults to columns[1].
        topojson: string, default None
            If using a TopoJSON, passing "objects.yourfeature" to the topojson
            keyword argument will enable conversion to GeoJSON.
        reset: boolean, default False
            Remove all current geoJSON layers, start with new layer

        Output
        ------
        GeoJSON data layer in obj.template_vars

        Example
        -------
        >>>map.geo_json(geo_path='us-states.json', line_color='blue', line_weight=3)
        >>>map.geo_json(geo_path='geo.json', data=df, columns=['Data 1', 'Data 2'],
                        key_on='feature.properties.myvalue', fill_color='PuBu',
                        threshold_scale=[0, 20, 30, 40, 50, 60])
        >>>map.geo_json(geo_path='countries.json', topojson='objects.countries')
        '''

        if reset:
            reset_vars = ['json_paths', 'func_vars', 'color_scales', 'geo_styles',
                          'gjson_layers', 'map_legends', 'topo_convert']
            for var in reset_vars:
                self.template_vars.update({var: []})
            self.mark_cnt['geojson'] = 0

        def json_style(style_cnt, line_color, line_weight, line_opacity,
                       fill_color, fill_opacity, quant_fill):
            '''Generate JSON styling function from template'''
            style_temp = self.env.get_template('geojson_style.js')
            style = style_temp.render({'style': style_cnt,
                                       'line_color': line_color,
                                       'line_weight': line_weight,
                                       'line_opacity': line_opacity,
                                       'fill_color': fill_color,
                                       'fill_opacity': fill_opacity,
                                       'quantize_fill': quant_fill})
            return style

        #Set map type to geojson
        self.map_type = 'geojson'

        #Set counter for GeoJSON and set iterations
        self.mark_cnt['geojson'] = self.mark_cnt.get('geojson', 0) + 1

        #Get JSON map layer template pieces, convert TopoJSON if necessary
        geo_path = ".defer(d3.json, '{0}')".format(geo_path)
        if topojson is None:
            map_var = '_'.join(['gjson', str(self.mark_cnt['geojson'])])
            layer_var = map_var
        else:
            map_var = '_'.join(['tjson', str(self.mark_cnt['geojson'])])
            topo_obj = '.'.join([map_var, topojson])
            layer_var = '_'.join(['topo', str(self.mark_cnt['geojson'])])
            topo_templ = self.env.get_template('topo_func.js')
            topo_func = topo_templ.render({'map_var': layer_var,
                                           't_var': map_var,
                                           't_var_obj': topo_obj})
            topo_lib = self.env.get_template('topojson_ref.txt').render()
            self.template_vars.update({'topojson': topo_lib})
            self.template_vars.setdefault('topo_convert',
                                          []).append(topo_func)

        style_count = '_'.join(['style', str(self.mark_cnt['geojson'])])

        #Get Data binding pieces if available
        if data is not None:

            import pandas as pd

            #Create DataFrame with only the relevant columns
            if isinstance(data, pd.DataFrame):
                data = pd.concat([data[columns[0]], data[columns[1]]], axis=1)

            #Save data to JSON
            self.json_data[data_out] = utilities.transform_data(data)

            #Add data to queue
            d_path = ".defer(d3.json, '{0}')".format(data_out)
            self.template_vars.setdefault('json_paths', []).append(d_path)

            #Add data variable to makeMap function
            data_var = '_'.join(['data', str(self.mark_cnt['geojson'])])
            self.template_vars.setdefault('func_vars', []).append(data_var)

            #D3 Color scale
            series = data[columns[1]]
            domain = threshold_scale or utilities.split_six(series=series)
            if len(domain) > 6:
                raise ValueError('The threshold scale must be of length <= 6')
            if not utilities.color_brewer(fill_color):
                raise ValueError('Please pass a valid color brewer code to '
                                 'fill_local. See docstring for valid codes.')

            palette = utilities.color_brewer(fill_color)
            d3range = palette[0: len(domain) + 1]

            color_temp = self.env.get_template('d3_threshold.js')
            d3scale = color_temp.render({'domain': domain,
                                         'range': d3range})
            self.template_vars.setdefault('color_scales', []).append(d3scale)

            #Create legend
            name = legend_name or columns[1]
            leg_templ = self.env.get_template('d3_map_legend.js')
            legend = leg_templ.render({'lin_max': int(domain[-1]*1.1),
                                       'caption': name})
            self.template_vars.setdefault('map_legends', []).append(legend)

            #Style with color brewer colors
            matchColor = 'color(matchKey({0}, {1}))'.format(key_on, data_var)
            style = json_style(style_count, line_color, line_weight,
                               line_opacity, None, fill_opacity, matchColor)
        else:
            style = json_style(style_count, line_color, line_weight,
                               line_opacity, fill_color, fill_opacity, None)

        layer = ('gJson_layer_{0} = L.geoJson({1}, {{style: {2}}}).addTo(map)'
                 .format(self.mark_cnt['geojson'], layer_var, style_count))

        self.template_vars.setdefault('json_paths', []).append(geo_path)
        self.template_vars.setdefault('func_vars', []).append(map_var)
        self.template_vars.setdefault('geo_styles', []).append(style)
        self.template_vars.setdefault('gjson_layers', []).append(layer)
Esempio n. 2
0
    def geo_json(self,
                 geo_path=None,
                 data_out='data.json',
                 data=None,
                 columns=None,
                 key_on=None,
                 threshold_scale=None,
                 fill_color='blue',
                 fill_opacity=0.6,
                 line_color='black',
                 line_weight=1,
                 line_opacity=1,
                 legend_name=None,
                 topojson=None,
                 reset=False):
        '''Apply a GeoJSON overlay to the map.

        Plot a GeoJSON overlay on the base map. There is no requirement
        to bind data (passing just a GeoJSON plots a single-color overlay),
        but there is a data binding option to map your columnar data to
        different feature objects with a color scale.

        If data is passed as a Pandas dataframe, the "columns" and "key-on"
        keywords must be included, the first to indicate which DataFrame
        columns to use, the second to indicate the layer in the GeoJSON
        on which to key the data. The 'columns' keyword does not need to be
        passed for a Pandas series.

        Colors are generated from color brewer (http://colorbrewer2.org/)
        sequential palettes on a D3 threshold scale. The scale defaults to the
        following quantiles: [0, 0.5, 0.75, 0.85, 0.9]. A custom scale can be
        passed to `threshold_scale` of length <=6, in order to match the
        color brewer range.

        TopoJSONs can be passed as "geo_path", but the "topojson" keyword must
        also be passed with the reference to the topojson objects to convert.
        See the topojson.feature method in the TopoJSON API reference:
        https://github.com/mbostock/topojson/wiki/API-Reference


        Parameters
        ----------
        geo_path: string, default None
            URL or File path to your GeoJSON data
        data_out: string, default 'data.json'
            Path to write Pandas DataFrame/Series to JSON if binding data
        data: Pandas DataFrame or Series, default None
            Data to bind to the GeoJSON.
        columns: dict or tuple, default None
            If the data is a Pandas DataFrame, the columns of data to be bound.
            Must pass column 1 as the key, and column 2 the values.
        key_on: string, default None
            Variable in the GeoJSON file to bind the data to. Must always
            start with 'feature' and be in JavaScript objection notation.
            Ex: 'feature.id' or 'feature.properties.statename'.
        threshold_scale: list, default None
            Data range for D3 threshold scale. Defaults to the following range
            of quantiles: [0, 0.5, 0.75, 0.85, 0.9], rounded to the nearest
            order-of-magnitude integer. Ex: 270 rounds to 200, 5600 to 6000.
        fill_color: string, default 'blue'
            Area fill color. Can pass a hex code, color name, or if you are
            binding data, one of the following color brewer palettes:
            'BuGn', 'BuPu', 'GnBu', 'OrRd', 'PuBu', 'PuBuGn', 'PuRd', 'RdPu',
            'YlGn', 'YlGnBu', 'YlOrBr', and 'YlOrRd'.
        fill_opacity: float, default 0.6
            Area fill opacity, range 0-1.
        line_color: string, default 'black'
            GeoJSON geopath line color.
        line_weight: int, default 1
            GeoJSON geopath line weight.
        line_opacity: float, default 1
            GeoJSON geopath line opacity, range 0-1.
        legend_name: string, default None
            Title for data legend. If not passed, defaults to columns[1].
        topojson: string, default None
            If using a TopoJSON, passing "objects.yourfeature" to the topojson
            keyword argument will enable conversion to GeoJSON.
        reset: boolean, default False
            Remove all current geoJSON layers, start with new layer

        Output
        ------
        GeoJSON data layer in obj.template_vars

        Example
        -------
        >>>map.geo_json(geo_path='us-states.json', line_color='blue', line_weight=3)
        >>>map.geo_json(geo_path='geo.json', data=df, columns=['Data 1', 'Data 2'],
                        key_on='feature.properties.myvalue', fill_color='PuBu',
                        threshold_scale=[0, 20, 30, 40, 50, 60])
        >>>map.geo_json(geo_path='countries.json', topojson='objects.countries')
        '''

        if reset:
            reset_vars = [
                'json_paths', 'func_vars', 'color_scales', 'geo_styles',
                'gjson_layers', 'map_legends', 'topo_convert'
            ]
            for var in reset_vars:
                self.template_vars.update({var: []})
            self.mark_cnt['geojson'] = 0

        def json_style(style_cnt, line_color, line_weight, line_opacity,
                       fill_color, fill_opacity, quant_fill):
            '''Generate JSON styling function from template'''
            style_temp = self.env.get_template('geojson_style.js')
            style = style_temp.render({
                'style': style_cnt,
                'line_color': line_color,
                'line_weight': line_weight,
                'line_opacity': line_opacity,
                'fill_color': fill_color,
                'fill_opacity': fill_opacity,
                'quantize_fill': quant_fill
            })
            return style

        #Set map type to geojson
        self.map_type = 'geojson'

        #Set counter for GeoJSON and set iterations
        self.mark_cnt['geojson'] = self.mark_cnt.get('geojson', 0) + 1

        #Get JSON map layer template pieces, convert TopoJSON if necessary
        geo_path = ".defer(d3.json, '{0}')".format(geo_path)
        if topojson is None:
            map_var = '_'.join(['gjson', str(self.mark_cnt['geojson'])])
            layer_var = map_var
        else:
            map_var = '_'.join(['tjson', str(self.mark_cnt['geojson'])])
            topo_obj = '.'.join([map_var, topojson])
            layer_var = '_'.join(['topo', str(self.mark_cnt['geojson'])])
            topo_templ = self.env.get_template('topo_func.js')
            topo_func = topo_templ.render({
                'map_var': layer_var,
                't_var': map_var,
                't_var_obj': topo_obj
            })
            topo_lib = self.env.get_template('topojson_ref.txt').render()
            self.template_vars.update({'topojson': topo_lib})
            self.template_vars.setdefault('topo_convert', []).append(topo_func)

        style_count = '_'.join(['style', str(self.mark_cnt['geojson'])])

        #Get Data binding pieces if available
        if data is not None:

            #Create DataFrame with only the relevant columns
            if isinstance(data, pd.DataFrame):
                data = pd.concat([data[columns[0]], data[columns[1]]], axis=1)

            #Save data to JSON
            self.json_data[data_out] = utilities.transform_data(data)

            #Add data to queue
            d_path = ".defer(d3.json, '{0}')".format(data_out)
            self.template_vars.setdefault('json_paths', []).append(d_path)

            #Add data variable to makeMap function
            data_var = '_'.join(['data', str(self.mark_cnt['geojson'])])
            self.template_vars.setdefault('func_vars', []).append(data_var)

            #D3 Color scale
            series = data[columns[1]]
            domain = threshold_scale or utilities.split_six(series=series)
            if len(domain) > 6:
                raise ValueError('The threshold scale must be of length <= 6')
            if not utilities.color_brewer(fill_color):
                raise ValueError('Please pass a valid color brewer code to '
                                 'fill_local. See docstring for valid codes.')

            palette = utilities.color_brewer(fill_color)
            d3range = palette[0:len(domain) + 1]

            color_temp = self.env.get_template('d3_threshold.js')
            d3scale = color_temp.render({'domain': domain, 'range': d3range})
            self.template_vars.setdefault('color_scales', []).append(d3scale)

            #Create legend
            name = legend_name or columns[1]
            leg_templ = self.env.get_template('d3_map_legend.js')
            legend = leg_templ.render({
                'lin_max': int(domain[-1] * 1.1),
                'caption': name
            })
            self.template_vars.setdefault('map_legends', []).append(legend)

            #Style with color brewer colors
            matchColor = 'color(matchKey({0}, {1}))'.format(key_on, data_var)
            style = json_style(style_count, line_color, line_weight,
                               line_opacity, None, fill_opacity, matchColor)
        else:
            style = json_style(style_count, line_color, line_weight,
                               line_opacity, fill_color, fill_opacity, None)

        layer = ('gJson_layer_{0} = L.geoJson({1}, {{style: {2}}}).addTo(map)'.
                 format(self.mark_cnt['geojson'], layer_var, style_count))

        self.template_vars.setdefault('json_paths', []).append(geo_path)
        self.template_vars.setdefault('func_vars', []).append(map_var)
        self.template_vars.setdefault('geo_styles', []).append(style)
        self.template_vars.setdefault('gjson_layers', []).append(layer)