diff --git a/examples/notebooks/multi-layer-example.ipynb b/examples/notebooks/multi-layer-example.ipynb
new file mode 100644
index 0000000..1d1980b
--- /dev/null
+++ b/examples/notebooks/multi-layer-example.ipynb
@@ -0,0 +1,158 @@
+{
+ "cells": [
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "import os\n",
+ "\n",
+ "import pandas as pd\n",
+ "\n",
+ "from mapboxgl.map import Map\n",
+ "from mapboxgl.layers import CircleLayer, GraduatedCircleLayer\n",
+ "from mapboxgl.utils import *\n",
+ "\n",
+ "\n",
+ "# Must be a public token, starting with `pk`\n",
+ "token = os.getenv('MAPBOX_ACCESS_TOKEN')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Load data from sample csv\n",
+ "data_url = 'https://raw.githubusercontent.com/mapbox/mapboxgl-jupyter/master/examples/data/points.csv'\n",
+ "df = pd.read_csv(data_url).round(3)\n",
+ "\n",
+ "df_to_geojson(df, \n",
+ " filename='../data/healthcare_points.geojson',\n",
+ " properties=['Avg Medicare Payments', 'Avg Covered Charges', 'date'], \n",
+ " lat='lat', \n",
+ " lon='lon', \n",
+ " precision=3)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {
+ "collapsed": true
+ },
+ "outputs": [],
+ "source": [
+ "measure_color = 'Avg Covered Charges'\n",
+ "color_breaks = [round(df[measure_color].quantile(q=x*0.1), 2) for x in range(2, 10)]\n",
+ "color_stops = create_color_stops(color_breaks, colors='Blues')\n",
+ "\n",
+ "# Generate radius breaks from data domain and circle-radius range\n",
+ "measure_radius = 'Avg Medicare Payments'\n",
+ "radius_breaks = [round(df[measure_radius].quantile(q=x*0.1), 2) for x in range(2, 10)]\n",
+ "radius_stops = create_radius_stops(radius_breaks, 0.5, 10)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Just show a map of the data\n",
+ "mv = Map(\n",
+ " access_token=token,\n",
+ " opacity=0.8,\n",
+ " center=(-96, 37.8),\n",
+ " zoom=3)\n",
+ "\n",
+ "viz = CircleLayer(\n",
+ " '../data/healthcare_points.geojson', \n",
+ " access_token=token, \n",
+ " radius=2)\n",
+ "\n",
+ "mv.add_layer(viz)\n",
+ "\n",
+ "mv.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "# Just show a map of the data\n",
+ "mv = Map(\n",
+ " access_token=token,\n",
+ " opacity=0.8,\n",
+ " center=(-96, 37.8),\n",
+ " zoom=3)\n",
+ "\n",
+ "viz = CircleLayer(\n",
+ " '../data/healthcare_points.geojson', \n",
+ " access_token=token, \n",
+ " radius=2)\n",
+ "\n",
+ "mv.add_layer(viz)\n",
+ "\n",
+ "viz2 = GraduatedCircleLayer(\n",
+ " '../data/healthcare_points.geojson', \n",
+ " access_token=token,\n",
+ " color_property=\"Avg Covered Charges\",\n",
+ " color_stops=color_stops,\n",
+ " radius_property=\"Avg Medicare Payments\",\n",
+ " radius_stops=radius_stops,\n",
+ " stroke_color='black',\n",
+ " stroke_width=0.5,\n",
+ " opacity=0.5,\n",
+ " below_layer='waterway-label')\n",
+ "\n",
+ "mv.add_layer(viz2)\n",
+ "\n",
+ "mv.show()"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "mv = Map(\n",
+ " access_token=token,\n",
+ " opacity=0.8,\n",
+ " center=(-96, 37.8),\n",
+ " zoom=3)\n",
+ "\n",
+ "mv.add_layer(viz2)\n",
+ "mv.add_layer(viz)\n",
+ "\n",
+ "mv.show()"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3",
+ "version": "3.5.4"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/mapboxgl/__init__.py b/mapboxgl/__init__.py
index 0efa1e2..79c4079 100644
--- a/mapboxgl/__init__.py
+++ b/mapboxgl/__init__.py
@@ -1,4 +1,44 @@
-from .viz import CircleViz, GraduatedCircleViz, HeatmapViz, ClusteredCircleViz, ImageViz, RasterTilesViz, ChoroplethViz, LinestringViz
+from .viz import (
+ CircleViz,
+ GraduatedCircleViz,
+ HeatmapViz,
+ ClusteredCircleViz,
+ ImageViz,
+ RasterTilesViz,
+ ChoroplethViz,
+ LinestringViz,
+)
+
+from .layers import (
+ CircleLayer,
+ GraduatedCircleLayer,
+ HeatmapLayer,
+ ClusteredCircleLayer,
+ ImageLayer,
+ RasterTilesLayer,
+ ChoroplethLayer,
+ LinestringLayer,
+)
+
+from .map import Map
__version__ = "0.9.0"
-__all__ = ['CircleViz', 'GraduatedCircleViz', 'HeatmapViz', 'ClusteredCircleViz', 'ImageViz', 'RasterTilesViz', 'ChoroplethViz', 'LinestringViz']
+__all__ = [
+ 'CircleViz',
+ 'GraduatedCircleViz',
+ 'HeatmapViz',
+ 'ClusteredCircleViz',
+ 'ImageViz',
+ 'RasterTilesViz',
+ 'ChoroplethViz',
+ 'LinestringViz',
+ 'CircleLayer',
+ 'GraduatedCircleLayer',
+ 'HeatmapLayer',
+ 'ClusteredCircleLayer',
+ 'ImageLayer',
+ 'RasterTilesLayer',
+ 'ChoroplethLayer',
+ 'LinestringLayer',
+ 'Map',
+]
\ No newline at end of file
diff --git a/mapboxgl/layers.py b/mapboxgl/layers.py
new file mode 100644
index 0000000..a645bfe
--- /dev/null
+++ b/mapboxgl/layers.py
@@ -0,0 +1,745 @@
+import json
+
+import numpy
+
+from mapboxgl.utils import color_map, height_map
+from mapboxgl import templates
+from mapboxgl.utils import img_encode, numeric_map
+
+
+class AbstractLayer():
+ """Abstract class for layers"""
+
+ def __init__(self,
+ data,
+ opacity=1,
+ below_layer='',
+ legend=True,
+ legend_layout='vertical',
+ legend_gradient=False,
+ legend_text_numeric_precision=None,
+ legend_key_shape='square',
+ min_zoom=0,
+ max_zoom=24,
+ *args,
+ **kwargs):
+ """Abstract class for layers
+
+ :param opacity: opacity of map data layer
+ :param below_layer: the layer to put this one below
+ :param legend: boolean for whether to show legend on map
+ :param legend_layout: determines if horizontal or vertical legend used
+ :param legend_gradient: boolean to determine if legend keys are discrete or gradient
+ :param legend_fill: string background color for legend, default is white
+ :param legend_header_fill: string background color for legend header (in vertical layout), default is #eee
+ :param legend_text_numeric_precision: decimal precision for numeric legend values
+ :param legend_key_shape: shape of the legend item keys, default varies by viz type; one of square, contiguous_bar, rounded-square, circle, line
+ """
+ self.data = data
+ self.opacity = opacity
+ self.below_layer = below_layer
+ self.legend = legend
+ self.legend_layout = legend_layout
+ self.legend_gradient = legend_gradient
+ self.legend_text_numeric_precision = legend_text_numeric_precision
+ self.legend_key_shape = legend_key_shape
+ self.min_zoom = min_zoom
+ self.max_zoom = max_zoom
+
+ self.layer_id = None
+ self.template = None
+ self.label_property = None
+
+ def create_html(self):
+ options = dict(
+ opacity=self.opacity,
+ belowLayer=self.below_layer,
+ showLegend=self.legend,
+ legendLayout=self.legend_layout,
+ legendGradient=json.dumps(self.legend_gradient),
+ legendNumericPrecision=json.dumps(self.legend_text_numeric_precision),
+ legendKeyShape=self.legend_key_shape,
+ layerId=self.layer_id,
+ template=self.template,
+ minZoom=self.min_zoom,
+ maxZoom=self.max_zoom
+ )
+
+ if self.label_property is None:
+ options.update(labelProperty=None)
+ else:
+ options.update(labelProperty='{' + self.label_property + '}')
+
+ self.get_layer_specific_variables(options)
+
+ return templates.format(self.template, **options)
+
+ def get_layer_specific_variables(self, options):
+ pass
+
+
+class CircleLayer(AbstractLayer):
+ """Create a circle layer"""
+
+ def __init__(self,
+ data,
+ label_property=None,
+ label_size=8,
+ label_color='#131516',
+ label_halo_color='white',
+ label_halo_width=1,
+ radius=1,
+ color_property=None,
+ color_stops=None,
+ color_default='grey',
+ color_function_type='interpolate',
+ stroke_color='grey',
+ stroke_width=0.1,
+ legend_key_shape='circle',
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param label_property: property to use for marker label
+ :param label_size: size of label text
+ :param label_color: color of label text
+ :param label_halo_color: color of label text halo
+ :param label_halo_width: width of label text halo
+ :param color_property: property to determine circle color
+ :param color_stops: property to determine circle color
+ :param color_default: property to determine default circle color if match lookup fails
+ :param color_function_type: property to determine `type` used by Mapbox to assign color
+ :param radius: radius of circle
+ :param stroke_color: color of circle stroke outline
+ :param stroke_width: with of circle stroke outline
+
+ """
+ super(CircleLayer, self).__init__(data, *args, **kwargs)
+
+ self.template = 'circle'
+ self.label_property = label_property
+ self.label_color = label_color
+ self.label_size = label_size
+ self.label_halo_color = label_halo_color
+ self.label_halo_width = label_halo_width
+ self.color_property = color_property
+ self.color_stops = color_stops
+ self.radius = radius
+ self.stroke_color = stroke_color
+ self.stroke_width = stroke_width
+ self.color_function_type = color_function_type
+ self.color_default = color_default
+ self.legend_key_shape = legend_key_shape
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to circle visual"""
+ options.update(dict(
+ geojson_data=json.dumps(self.data, ensure_ascii=False),
+ colorProperty=self.color_property,
+ colorType=self.color_function_type,
+ colorStops=self.color_stops,
+ strokeWidth=self.stroke_width,
+ strokeColor=self.stroke_color,
+ radius=self.radius,
+ defaultColor=self.color_default,
+ labelColor=self.label_color,
+ labelSize=self.label_size,
+ labelHaloColor=self.label_halo_color,
+ labelHaloWidth=self.label_halo_width,
+ ))
+
+
+class GraduatedCircleLayer(AbstractLayer):
+ """Create a graduated circle layer"""
+
+ def __init__(self,
+ data,
+ label_property=None,
+ label_size=8,
+ label_color='#131516',
+ label_halo_color='white',
+ label_halo_width=1,
+ color_property=None,
+ color_stops=None,
+ color_default='grey',
+ color_function_type='interpolate',
+ stroke_color='grey',
+ stroke_width=0.1,
+ radius_property=None,
+ radius_stops=None,
+ radius_default=2,
+ radius_function_type='interpolate',
+ legend_key_shape='circle',
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param label_property: property to use for marker label
+ :param color_property: property to determine circle color
+ :param color_stops: property to determine circle color
+ :param color_default: property to determine default circle color if match lookup fails
+ :param color_function_type: property to determine `type` used by Mapbox to assign color
+ :param radius_property: property to determine circle radius
+ :param radius_stops: property to determine circle radius
+ :param radius_default: property to determine default circle radius if match lookup fails
+ :param radius_function_type: property to determine `type` used by Mapbox to assign radius size
+ :param stroke_color: color of circle stroke outline
+ :param stroke_width: with of circle stroke outline
+
+ """
+ super(GraduatedCircleLayer, self).__init__(data, *args, **kwargs)
+
+ self.template = 'graduated_circle'
+ self.label_property = label_property
+ self.label_color = label_color
+ self.label_size = label_size
+ self.label_halo_color = label_halo_color
+ self.label_halo_width = label_halo_width
+ self.color_property = color_property
+ self.color_stops = color_stops
+ self.radius_property = radius_property
+ self.radius_stops = radius_stops
+ self.color_function_type = color_function_type
+ self.color_default = color_default
+ self.radius_function_type = radius_function_type
+ self.radius_default = radius_default
+ self.stroke_color = stroke_color
+ self.stroke_width = stroke_width
+ self.legend_key_shape = legend_key_shape
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to graduated circle visual"""
+ options.update(dict(
+ geojson_data=json.dumps(self.data, ensure_ascii=False),
+ colorProperty=self.color_property,
+ colorStops=self.color_stops,
+ colorType=self.color_function_type,
+ radiusType=self.radius_function_type,
+ defaultColor=self.color_default,
+ defaultRadius=self.radius_default,
+ radiusProperty=self.radius_property,
+ radiusStops=self.radius_stops,
+ strokeWidth=self.stroke_width,
+ strokeColor=self.stroke_color,
+ labelColor=self.label_color,
+ labelSize=self.label_size,
+ labelHaloColor=self.label_halo_color,
+ labelHaloWidth=self.label_halo_width
+ ))
+
+
+class HeatmapLayer(AbstractLayer):
+ """Create a heatmap layer"""
+
+ def __init__(self,
+ data,
+ label_property=None,
+ weight_property=None,
+ weight_stops=None,
+ color_stops=None,
+ radius_stops=None,
+ intensity_stops=None,
+ *args,
+ **kwargs):
+ """Create a heatmap layer
+
+ :param weight_property: property to determine heatmap weight. EX. "population"
+ :param weight_stops: stops to determine heatmap weight. EX. [[10, 0], [100, 1]]
+ :param color_stops: stops to determine heatmap color. EX. [[0, "red"], [0.5, "blue"], [1, "green"]]
+ :param radius_stops: stops to determine heatmap radius based on zoom. EX: [[0, 1], [12, 30]]
+ :param intensity_stops: stops to determine the heatmap intensity based on zoom. EX: [[0, 0.1], [20, 5]]
+
+ """
+ super(HeatmapLayer, self).__init__(data, *args, **kwargs)
+
+ self.template = 'heatmap'
+ self.label_property = label_property
+ self.weight_property = weight_property
+ self.weight_stops = weight_stops
+ if color_stops:
+ # Make the first color stop in a heatmap have opacity 0 for good visual effect
+ self.color_stops = [[0.00001, 'rgba(0,0,0,0)']] + color_stops
+ self.radius_stops = radius_stops
+ self.intensity_stops = intensity_stops
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to heatmap visual"""
+ options.update(dict(
+ geojson_data=json.dumps(self.data, ensure_ascii=False),
+ colorStops=self.color_stops,
+ radiusStops=self.radius_stops,
+ weightProperty=self.weight_property,
+ weightStops=self.weight_stops,
+ intensityStops=self.intensity_stops,
+ ))
+
+
+class ClusteredCircleLayer(AbstractLayer):
+ """Create a clustered circle layer"""
+
+ def __init__(self,
+ data,
+ label_property=None,
+ label_size=8,
+ label_color='#131516',
+ label_halo_color='white',
+ label_halo_width=1,
+ color_stops=None,
+ radius_stops=None,
+ cluster_radius=30,
+ cluster_maxzoom=14,
+ radius_default=2,
+ color_default='black',
+ stroke_color='grey',
+ stroke_width=0.1,
+ legend_key_shape='circle',
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param color_property: property to determine circle color
+ :param color_stops: property to determine circle color
+ :param radius_property: property to determine circle radius
+ :param radius_stops: property to determine circle radius
+ :param stroke_color: color of circle stroke outline
+ :param stroke_width: with of circle stroke outline
+ :param radius_default: radius of circles not contained in a cluster
+ :param color_default: color of circles not contained in a cluster
+
+ """
+ super(ClusteredCircleLayer, self).__init__(data, *args, **kwargs)
+
+ self.template = 'clustered_circle'
+ self.label_property = label_property
+ self.label_color = label_color
+ self.label_size = label_size
+ self.label_halo_color = label_halo_color
+ self.label_halo_width = label_halo_width
+ self.color_stops = color_stops
+ self.radius_stops = radius_stops
+ self.clusterRadius = cluster_radius
+ self.clusterMaxZoom = cluster_maxzoom
+ self.radius_default = radius_default
+ self.color_default = color_default
+ self.stroke_color = stroke_color
+ self.stroke_width = stroke_width
+ self.color_default = color_default
+ self.legend_key_shape = legend_key_shape
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to a clustered circle visual"""
+ options.update(dict(
+ geojson_data=json.dumps(self.data, ensure_ascii=False),
+ colorStops=self.color_stops,
+ colorDefault=self.color_default,
+ radiusStops=self.radius_stops,
+ clusterRadius=self.clusterRadius,
+ clusterMaxZoom=self.clusterMaxZoom,
+ strokeWidth=self.stroke_width,
+ strokeColor=self.stroke_color,
+ radiusDefault=self.radius_default,
+ labelColor=self.label_color,
+ labelSize=self.label_size,
+ labelHaloColor=self.label_halo_color,
+ labelHaloWidth=self.label_halo_width
+ ))
+
+
+class ChoroplethLayer(AbstractLayer):
+ """Create a choropleth layer"""
+
+ def __init__(self,
+ data,
+ vector_url=None,
+ vector_layer_name=None,
+ vector_join_property=None,
+ data_join_property=None, # vector only
+ label_property=None,
+ color_property=None,
+ color_stops=None,
+ color_default='grey',
+ color_function_type='interpolate',
+ line_color='white',
+ line_stroke='solid',
+ line_width=1,
+ height_property=None,
+ height_stops=None,
+ height_default=0.0,
+ height_function_type='interpolate',
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param data: can be either GeoJSON (containing polygon features) or JSON for data-join technique with vector polygons
+ :param vector_url: optional property to define vector polygon source
+ :param vector_layer_name: property to define target layer of vector source
+ :param vector_join_property: property to aid in determining color for styling vector polygons
+ :param data_join_property: property to join json data to vector features
+ :param label_property: property to use for marker label
+ :param color_property: property to determine polygon color
+ :param color_stops: property to determine polygon color
+ :param color_default: property to determine default polygon color if match lookup fails
+ :param color_function_type: property to determine `type` used by Mapbox to assign color
+ :param line_color: property to determine choropleth line color
+ :param line_stroke: property to determine choropleth line stroke (solid, dashed, dotted, dash dot)
+ :param line_width: property to determine choropleth line width
+ :param height_property: feature property for determining polygon height in 3D extruded choropleth map
+ :param height_stops: property for determining 3D extrusion height
+ :param height_default: default height for 3D extruded polygons
+ :param height_function_type: roperty to determine `type` used by Mapbox to assign height
+ """
+ super(ChoroplethLayer, self).__init__(data, *args, **kwargs)
+
+ self.vector_url = vector_url
+ self.vector_layer_name = vector_layer_name
+ self.vector_join_property = vector_join_property
+ self.data_join_property = data_join_property
+
+ if self.vector_url is not None and self.vector_layer_name is not None:
+ self.template = 'vector_choropleth'
+ self.vector_source = True
+ else:
+ self.template = 'choropleth'
+ self.vector_source = False
+
+ self.label_property = label_property
+ self.color_property = color_property
+ self.color_stops = color_stops
+ self.color_default = color_default
+ self.color_function_type = color_function_type
+ self.line_color = line_color
+ self.line_stroke = line_stroke
+ self.line_width = line_width
+ self.height_property = height_property
+ self.height_stops = height_stops
+ self.height_default = height_default
+ self.height_function_type = height_function_type
+
+ def generate_vector_color_map(self):
+ """Generate color stops array for use with match expression in mapbox template"""
+ vector_stops = []
+ for row in self.data:
+ # map color to JSON feature using color_property
+ color = color_map(row[self.color_property], self.color_stops, self.color_default)
+
+ # link to vector feature using data_join_property (from JSON object)
+ vector_stops.append([row[self.data_join_property], color])
+
+ return vector_stops
+
+ def generate_vector_height_map(self):
+ """Generate height stops array for use with match expression in mapbox template"""
+ vector_stops = []
+
+ if self.height_function_type == 'match':
+ match_height = self.height_stops
+
+ for row in self.data:
+ # map height to JSON feature using height_property
+ height = height_map(row[self.height_property], self.height_stops, self.height_default)
+
+ # link to vector feature using data_join_property (from JSON object)
+ vector_stops.append([row[self.data_join_property], height])
+
+ return vector_stops
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to heatmap visual"""
+
+ # set line stroke dash interval based on line_stroke property
+ if self.line_stroke in ["dashed", "--"]:
+ self.line_dash_array = [6, 4]
+ elif self.line_stroke in ["dotted", ":"]:
+ self.line_dash_array = [0.5, 4]
+ elif self.line_stroke in ["dash dot", "-."]:
+ self.line_dash_array = [6, 4, 0.5, 4]
+ elif self.line_stroke in ["solid", "-"]:
+ self.line_dash_array = [1, 0]
+ else:
+ # default to solid line
+ self.line_dash_array = [1, 0]
+
+ # check if choropleth map should include 3-D extrusion
+ self.extrude = all([bool(self.height_property), bool(self.height_stops)])
+
+ # common variables for vector and geojson-based choropleths
+ options.update(dict(
+ colorStops=self.color_stops,
+ colorProperty=self.color_property,
+ colorType=self.color_function_type,
+ defaultColor=self.color_default,
+ lineColor=self.line_color,
+ lineDashArray=self.line_dash_array,
+ lineStroke=self.line_stroke,
+ lineWidth=self.line_width,
+ extrudeChoropleth=self.extrude
+ ))
+ if self.extrude:
+ options.update(dict(
+ heightType=self.height_function_type,
+ heightProperty=self.height_property,
+ heightStops=self.height_stops,
+ defaultHeight=self.height_default,
+ ))
+
+ # vector-based choropleth map variables
+ if self.vector_source:
+ options.update(dict(
+ vectorUrl=self.vector_url,
+ vectorLayer=self.vector_layer_name,
+ vectorColorStops=self.generate_vector_color_map(),
+ vectorJoinDataProperty=self.vector_join_property,
+ joinData=json.dumps(self.data, ensure_ascii=False),
+ dataJoinProperty=self.data_join_property,
+ ))
+ if self.extrude:
+ options.update(dict(
+ vectorHeightStops=self.generate_vector_height_map(),
+ ))
+
+ # geojson-based choropleth map variables
+ else:
+ options.update(dict(
+ geojson_data=json.dumps(self.data, ensure_ascii=False),
+ ))
+
+
+class ImageLayer(AbstractLayer):
+ """Create a image viz"""
+
+ def __init__(self,
+ image,
+ coordinates,
+ legend=False,
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param coordinates: property to determine image coordinates (UL, UR, LR, LL).
+ EX. [[-80.425, 46.437], [-71.516, 46.437], [-71.516, 37.936], [-80.425, 37.936]]
+ :param image: url, local path or a numpy ndarray
+ :param legend: default setting is to hide heatmap legend
+
+ """
+ super(ImageLayer, self).__init__(None, *args, **kwargs)
+
+ if type(image) is numpy.ndarray:
+ image = img_encode(image)
+
+ self.template = 'image'
+ self.image = image
+ self.coordinates = coordinates
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to image visual"""
+ options.update(dict(
+ image=self.image,
+ coordinates=self.coordinates))
+
+
+class RasterTilesLayer(AbstractLayer):
+ """Create a rastertiles map"""
+
+ def __init__(self,
+ tiles_url,
+ tiles_size=256,
+ tiles_bounds=None,
+ tiles_minzoom=0,
+ tiles_maxzoom=22,
+ legend=False,
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param tiles_url: property to determine tiles url endpoint
+ :param tiles_size: property to determine displayed tiles size
+ :param tiles_bounds: property to determine the tiles endpoint bounds
+ :param tiles_minzoom: property to determine the tiles endpoint min zoom
+ :param tiles_max: property to determine the tiles endpoint max zoom
+ :param legend: default setting is to hide heatmap legend
+
+ """
+ super(RasterTilesLayer, self).__init__(None, *args, **kwargs)
+
+ self.template = 'raster'
+ self.tiles_url = tiles_url
+ self.tiles_size = tiles_size
+ self.tiles_bounds = tiles_bounds
+ self.tiles_minzoom = tiles_minzoom
+ self.tiles_maxzoom = tiles_maxzoom
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to a raster visual"""
+ options.update(dict(
+ tiles_url=self.tiles_url,
+ tiles_size=self.tiles_size,
+ tiles_minzoom=self.tiles_minzoom,
+ tiles_maxzoom=self.tiles_maxzoom,
+ tiles_bounds=self.tiles_bounds if self.tiles_bounds else 'undefined'))
+
+
+class LinestringLayer(AbstractLayer):
+ """Create a linestring viz"""
+
+ def __init__(self,
+ data,
+ vector_url=None,
+ vector_layer_name=None,
+ vector_join_property=None,
+ data_join_property=None,
+ label_property=None,
+ label_size=8,
+ label_color='#131516',
+ label_halo_color='white',
+ label_halo_width=1,
+ color_property=None,
+ color_stops=None,
+ color_default='grey',
+ color_function_type='interpolate',
+ line_stroke='solid',
+ line_width_property=None,
+ line_width_stops=None,
+ line_width_default=1,
+ line_width_function_type='interpolate',
+ legend_key_shape='line',
+ *args,
+ **kwargs):
+ """Construct a Mapviz object
+
+ :param data: can be either GeoJSON (containing polygon features) or JSON for data-join technique with vector polygons
+ :param vector_url: optional property to define vector linestring source
+ :param vector_layer_name: property to define target layer of vector source
+ :param vector_join_property: property to aid in determining color for styling vector lines
+ :param data_join_property: property to join json data to vector features
+ :param label_property: property to use for marker label
+ :param label_size: size of label text
+ :param label_color: color of label text
+ :param label_halo_color: color of label text halo
+ :param label_halo_width: width of label text halo
+ :param color_property: property to determine line color
+ :param color_stops: property to determine line color
+ :param color_default: property to determine default line color if match lookup fails
+ :param color_function_type: property to determine `type` used by Mapbox to assign color
+ :param line_stroke: property to determine line stroke (solid, dashed, dotted, dash dot)
+ :param line_width_property: property to determine line width
+ :param line_width_stops: property to determine line width
+ :param line_width_default: property to determine default line width if match lookup fails
+ :param line_width_function_type: property to determine `type` used by Mapbox to assign line width
+
+ """
+ super(LinestringLayer, self).__init__(data, *args, **kwargs)
+
+ self.vector_url = vector_url
+ self.vector_layer_name = vector_layer_name
+ self.vector_join_property = vector_join_property
+ self.data_join_property = data_join_property
+
+ if self.vector_url is not None and self.vector_layer_name is not None:
+ self.template = 'vector_linestring'
+ self.vector_source = True
+ else:
+ self.vector_source = False
+ self.template = 'linestring'
+
+ self.label_property = label_property
+ self.label_color = label_color
+ self.label_size = label_size
+ self.label_halo_color = label_halo_color
+ self.label_halo_width = label_halo_width
+ self.color_property = color_property
+ self.color_stops = color_stops
+ self.color_default = color_default
+ self.color_function_type = color_function_type
+ self.line_stroke = line_stroke
+ self.line_width_property = line_width_property
+ self.line_width_stops = line_width_stops
+ self.line_width_default = line_width_default
+ self.line_width_function_type = line_width_function_type
+ self.legend_key_shape = legend_key_shape
+
+ def generate_vector_color_map(self):
+ """Generate color stops array for use with match expression in mapbox template"""
+ vector_stops = []
+ for row in self.data:
+ # map color to JSON feature using color_property
+ color = color_map(row[self.color_property], self.color_stops, self.color_default)
+
+ # link to vector feature using data_join_property (from JSON object)
+ vector_stops.append([row[self.data_join_property], color])
+
+ return vector_stops
+
+ def generate_vector_width_map(self):
+ """Generate width stops array for use with match expression in mapbox template"""
+ vector_stops = []
+
+ if self.line_width_function_type == 'match':
+ match_width = self.line_width_stops
+
+ for row in self.data:
+ # map width to JSON feature using width_property
+ width = numeric_map(row[self.line_width_property], self.line_width_stops, self.line_width_default)
+
+ # link to vector feature using data_join_property (from JSON object)
+ vector_stops.append([row[self.data_join_property], width])
+
+ return vector_stops
+
+ def get_layer_specific_variables(self, options):
+ """Update map template variables specific to linestring visual"""
+
+ # set line stroke dash interval based on line_stroke property
+ if self.line_stroke in ["dashed", "--"]:
+ self.line_dash_array = [6, 4]
+ elif self.line_stroke in ["dotted", ":"]:
+ self.line_dash_array = [0.5, 4]
+ elif self.line_stroke in ["dash dot", "-."]:
+ self.line_dash_array = [6, 4, 0.5, 4]
+ elif self.line_stroke in ["solid", "-"]:
+ self.line_dash_array = [1, 0]
+ else:
+ # default to solid line
+ self.line_dash_array = [1, 0]
+
+ # common variables for vector and geojson-based linestring maps
+ options.update(dict(
+ colorStops=self.color_stops,
+ colorProperty=self.color_property,
+ colorType=self.color_function_type,
+ defaultColor=self.color_default,
+ lineColor=self.color_default,
+ lineDashArray=self.line_dash_array,
+ lineStroke=self.line_stroke,
+ widthStops=self.line_width_stops,
+ widthProperty=self.line_width_property,
+ widthType=self.line_width_function_type,
+ defaultWidth=self.line_width_default,
+ labelColor=self.label_color,
+ labelSize=self.label_size,
+ labelHaloColor=self.label_halo_color,
+ labelHaloWidth=self.label_halo_width
+ ))
+
+ # vector-based linestring map variables
+ if self.vector_source:
+ options.update(dict(
+ vectorUrl=self.vector_url,
+ vectorLayer=self.vector_layer_name,
+ vectorJoinDataProperty=self.vector_join_property,
+ vectorColorStops=[[0, self.color_default]],
+ vectorWidthStops=[[0, self.line_width_default]],
+ joinData=json.dumps(self.data, ensure_ascii=False),
+ dataJoinProperty=self.data_join_property,
+ ))
+
+ if self.color_property:
+ options.update(dict(vectorColorStops=self.generate_vector_color_map()))
+
+ if self.line_width_property:
+ options.update(dict(vectorWidthStops=self.generate_vector_width_map()))
+
+ # geojson-based linestring map variables
+ else:
+ options.update(dict(
+ geojson_data=json.dumps(self.data, ensure_ascii=False),
+ ))
diff --git a/mapboxgl/map.py b/mapboxgl/map.py
new file mode 100644
index 0000000..cf394e6
--- /dev/null
+++ b/mapboxgl/map.py
@@ -0,0 +1,165 @@
+import codecs
+import json
+import os
+
+from IPython.core.display import HTML, display
+
+from mapboxgl.errors import TokenError
+from mapboxgl import templates
+
+
+GL_JS_VERSION = 'v0.49.0'
+
+
+class Map(object):
+
+ def __init__(self,
+ access_token=None,
+ center=(0, 0),
+ opacity=1,
+ div_id='map',
+ height='500px',
+ style='mapbox://styles/mapbox/light-v9?optimize=true',
+ width='100%',
+ zoom=0,
+ min_zoom=0,
+ max_zoom=24,
+ pitch=0,
+ bearing=0,
+ box_zoom_on=True,
+ double_click_zoom_on=True,
+ scroll_zoom_on=True,
+ touch_zoom_on=True,
+ legend_fill='white',
+ legend_header_fill='white',
+ legend_text_color='#6e6e6e',
+ legend_title_halo_color='white',
+ legend_key_borders_on=True
+ ):
+ """Construct a MapViz object
+
+ :param access_token: Mapbox GL JS access token.
+ :param center: map center point
+ :param style: url to mapbox style or stylesheet as a Python dictionary in JSON format
+ :param div_id: The HTML div id of the map container in the viz
+ :param width: The CSS width of the HTML div id in % or pixels.
+ :param height: The CSS height of the HTML map div in % or pixels.
+ :param zoom: starting zoom level for map
+ :param opacity: opacity of map data layer
+ :param pitch: starting pitch (in degrees) for map
+ :param bearing: starting bearing (in degrees) for map
+ :param box_zoom_on: boolean indicating if map can be zoomed to a region by dragging a bounding box
+ :param double_click_zoom_on: boolean indicating if map can be zoomed with double-click
+ :param scroll_zoom_on: boolean indicating if map can be zoomed with the scroll wheel
+ :param touch_zoom_on: boolean indicating if map can be zoomed with two-finger touch gestures
+ :param legend_fill: string background color for legend, default is white
+ :param legend_header_fill: string background color for legend header (in vertical layout), default is #eee
+ :param legend_text_color: string color for legend text default is #6e6e6e
+ :param legend_title_halo_color: color of legend title text halo
+ :param legend_key_borders_on: boolean for whether to show/hide legend key borders
+
+ """
+ if access_token is None:
+ access_token = os.environ.get('MAPBOX_ACCESS_TOKEN', '')
+ if access_token.startswith('sk'):
+ raise TokenError('Mapbox access token must be public (pk), not secret (sk). ' \
+ 'Please sign up at https://www.mapbox.com/signup/ to get a public token. ' \
+ 'If you already have an account, you can retreive your token at https://www.mapbox.com/account/.')
+ self.access_token = access_token
+ self.template = 'map'
+ self.div_id = div_id
+ self.width = width
+ self.height = height
+ self.style = style
+ self.center = center
+ self.zoom = zoom
+ self.opacity = opacity
+ self.label_property = None
+ self.min_zoom = min_zoom
+ self.max_zoom = max_zoom
+ self.pitch = pitch
+ self.bearing = bearing
+ self.box_zoom_on = box_zoom_on
+ self.double_click_zoom_on = double_click_zoom_on
+ self.scroll_zoom_on = scroll_zoom_on
+ self.touch_zoom_on = touch_zoom_on
+ self.legend_fill = legend_fill
+ self.legend_header_fill = legend_header_fill
+ self.legend_text_color = legend_text_color,
+ self.legend_title_halo_color = legend_title_halo_color
+ self.legend_key_borders_on = legend_key_borders_on
+ self.layer_id_counter = 0
+ self.layers = []
+
+ def as_iframe(self, html_data):
+ """Build the HTML representation for the mapviz."""
+
+ srcdoc = html_data.replace('"', "'")
+ return (''.format(
+ div_id=self.div_id,
+ srcdoc=srcdoc,
+ width=self.width,
+ height=self.height))
+
+ def show(self, **kwargs):
+ # Load the HTML iframe
+ html = self.create_html(**kwargs)
+ map_html = self.as_iframe(html)
+
+ # Display the iframe in the current jupyter notebook view
+ display(HTML(map_html))
+
+ def create_html(self, filename=None):
+ """Create a circle visual from a geojson data source"""
+ if isinstance(self.style, str):
+ style = "'{}'".format(self.style)
+ else:
+ style = self.style
+ options = dict(
+ gl_js_version=GL_JS_VERSION,
+ accessToken=self.access_token,
+ div_id=self.div_id,
+ style=style,
+ center=list(self.center),
+ zoom=self.zoom,
+ opacity=self.opacity,
+ minzoom=self.min_zoom,
+ maxzoom=self.max_zoom,
+ pitch=self.pitch,
+ bearing=self.bearing,
+ boxZoomOn=json.dumps(self.box_zoom_on),
+ doubleClickZoomOn=json.dumps(self.double_click_zoom_on),
+ scrollZoomOn=json.dumps(self.scroll_zoom_on),
+ touchZoomOn=json.dumps(self.touch_zoom_on),
+ legendFill=self.legend_fill,
+ legendHeaderFill=self.legend_header_fill,
+ legendTextColor=self.legend_text_color,
+ legendTitleHaloColor=self.legend_title_halo_color,
+ legendKeyBordersOn=json.dumps(self.legend_key_borders_on)
+ )
+
+ if self.label_property is None:
+ options.update(labelProperty=None)
+ else:
+ options.update(labelProperty='{' + self.label_property + '}')
+
+ html = []
+ html.append(templates.format(self.template, **options))
+ for layer in self.layers:
+ html.append(layer.create_html())
+ if filename:
+ with codecs.open(filename, "w", "utf-8-sig") as f:
+ f.write("\n".join(html))
+ return None
+ else:
+ return "\n".join(html)
+
+ def add_layer(self, layer):
+ self.layers.append(layer)
+ layer.layer_id = self.layer_id_counter
+ self.layer_id_counter +=1
+
+ def remove_layer(self, layer):
+ self.layers.remove(layer)
+ layer.show_legend = False
diff --git a/mapboxgl/templates/base.html b/mapboxgl/templates/base.html
deleted file mode 100644
index 2f664f7..0000000
--- a/mapboxgl/templates/base.html
+++ /dev/null
@@ -1,53 +0,0 @@
-{% extends "main.html" %}
-
-{% block javascript %}
-
- mapboxgl.accessToken = '{{ accessToken }}';
-
- var map = new mapboxgl.Map({
- container: 'map',
- attributionControl: false,
- style: {{ style }},
- center: {{ center }},
- zoom: {{ zoom }},
- pitch: {{ pitch }},
- bearing: {{ bearing }},
- scrollZoom: {{ scrollZoomOn|safe }},
- touchZoom: {{ touchZoomOn|safe }},
- doubleClickZoom: {{ doubleClickZoomOn|safe }},
- boxZoom: {{ boxZoomOn|safe }},
- transformRequest: (url, resourceType) => {
- if ( url.slice(0,22) == 'https://api.mapbox.com' ||
- url.slice(0,26) == 'https://a.tiles.mapbox.com' ||
- url.slice(0,26) == 'https://b.tiles.mapbox.com' ||
- url.slice(0,26) == 'https://c.tiles.mapbox.com' ||
- url.slice(0,26) == 'https://d.tiles.mapbox.com') {
- //Add Mapboxgl-Jupyter Plugin identifier for Mapbox API traffic
- return {
- url: [url.slice(0, url.indexOf("?")+1), "pluginName=PythonMapboxgl&", url.slice(url.indexOf("?")+1)].join('')
- }
- }
- else {
- //Do not transform URL for non Mapbox GET requests
- return {url: url}
- }
- },
- });
-
- {% block attribution %}
-
- map.addControl(new mapboxgl.AttributionControl({ compact: true }));
-
- {% endblock attribution %}
-
- {% block navigation %}
-
- map.addControl(new mapboxgl.NavigationControl());
-
- {% endblock navigation %}
-
- {% block legend %}{% endblock legend %}
-
- {% block map %}{% endblock map %}
-
-{% endblock %}
diff --git a/mapboxgl/templates/choropleth.html b/mapboxgl/templates/choropleth.html
index 59fb005..9719264 100644
--- a/mapboxgl/templates/choropleth.html
+++ b/mapboxgl/templates/choropleth.html
@@ -1,99 +1,44 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
{% block extra_css %}
-
+ .gradient.bordered, .legend-key.bordered { border: solid {{ lineColor }} {{ lineWidth }}px; }
{% endblock extra_css %}
-{% block legend %}
-
+{% block extra_javascript %}
{% if showLegend %}
{% if extrudeChoropleth %}
{% if colorStops and colorProperty and heightProperty %}
{% if colorProperty != heightProperty and extrudeChoropleth %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }} vs. {{ heightProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }} vs. {{ heightProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% else %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
{% endif %}
{% else %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
{% endif %}
-{% endblock legend %}
-
-{% block map %}
-
map.on('style.load', function() {
{% block choropleth %}
// Add geojson data source
- map.addSource("data", {
+ map.addSource("data{{ layerId }}", {
"type": "geojson",
"data": {{ geojson_data }},
"buffer": 1,
"maxzoom": 14
});
- // Add data layer
- map.addLayer({
- "id": "choropleth-fill",
- "source": "data",
- "type": "fill",
- "paint": {
- "fill-color": generatePropertyExpression("{{ colorType }}", "{{ colorProperty }}", {{ colorStops }}, "{{ defaultColor }}"),
- "fill-opacity": {{ opacity }}
- }
- }, "{{ belowLayer }}" );
-
- // Add border layer
- map.addLayer({
- "id": "choropleth-line",
- "source": "data",
- "type": "line",
- "layout": {
- "line-join": "round",
- "line-cap": "round"
- },
- "paint": {
- {% if lineDashArray %}
- "line-dasharray": {{ lineDashArray }},
- {% endif %}
- "line-color": "{{ lineColor }}",
- "line-width": {{ lineWidth }},
- "line-opacity": {{ opacity }}
- }
- }, "{{ belowLayer }}" );
-
- // Add label layer
- map.addLayer({
- "id": "choropleth-label",
- "source": "data",
- "type": "symbol",
- "layout": {
- {% if labelProperty %}
- "text-field": "{{ labelProperty }}",
- {% endif %}
- "text-size" : generateInterpolateExpression('zoom', [[0,8],[22,16]] ),
- "text-offset": [0,-1]
- },
- "paint": {
- "text-halo-color": "white",
- "text-halo-width": 1
- }
- }, "{{ belowLayer }}" );
-
// Optional extrusion layer
{% if extrudeChoropleth %}
map.addLayer({
- id: "choropleth-extrusion",
- type: "fill-extrusion",
- source: "data",
+ id: "choropleth-extrusion{{ layerId }}",
+ source: "data{{ layerId }}",
+ type: "fill-extrusion",
paint: {
"fill-extrusion-opacity": {{ opacity }},
"fill-extrusion-color": generatePropertyExpression("{{ colorType }}", "{{ colorProperty }}", {{ colorStops }}, "{{ defaultColor }}"),
@@ -101,51 +46,139 @@
}
}, "{{ belowLayer }}");
+ {% else %}
+
+ // Add data layer
+ map.addLayer({
+ "id": "choropleth-fill{{ layerId }}",
+ "source": "data{{ layerId }}",
+ "type": "fill",
+ "paint": {
+ "fill-color": generatePropertyExpression("{{ colorType }}", "{{ colorProperty }}", {{ colorStops }}, "{{ defaultColor }}"),
+ "fill-opacity": {{ opacity }}
+ }
+ }, "{{ belowLayer }}" );
+
+ // Add border layer
+ map.addLayer({
+ "id": "choropleth-line{{ layerId }}",
+ "source": "data{{ layerId }}",
+ "type": "line",
+ "layout": {
+ "line-join": "round",
+ "line-cap": "round"
+ },
+ "paint": {
+ {% if lineDashArray %}
+ "line-dasharray": {{ lineDashArray }},
+ {% endif %}
+ "line-color": "{{ lineColor }}",
+ "line-width": {{ lineWidth }},
+ "line-opacity": {{ opacity }}
+ }
+ }, "{{ belowLayer }}" );
+
+ // Add label layer
+ map.addLayer({
+ "id": "choropleth-label{{ layerId }}",
+ "source": "data{{ layerId }}",
+ "type": "symbol",
+ "layout": {
+ {% if labelProperty %}
+ "text-field": "{{ labelProperty }}",
+ {% endif %}
+ "text-size" : generateInterpolateExpression('zoom', [[0,8],[22,16]] ),
+ "text-offset": [0,-1]
+ },
+ "paint": {
+ "text-halo-color": "white",
+ "text-halo-width": 1
+ }
+ }, "{{ belowLayer }}" );
+
{% endif %}
{% endblock choropleth %}
- // Create a popup
- var popup = new mapboxgl.Popup({
- closeButton: false,
- closeOnClick: false
- });
-
{% block choropleth_popup %}
- // Show the popup on mouseover
- map.on('mousemove', 'choropleth-fill', function(e) {
- map.getCanvas().style.cursor = 'pointer';
-
- let f = e.features[0];
- let popup_html = '
';
-
- for (key in f.properties) {
- popup_html += '
' + key + ': ' + f.properties[key] + ' '
- }
-
- popup_html += ''
- popup.setLngLat(e.lngLat)
- .setHTML(popup_html)
- .addTo(map);
- });
+ {% if extrudeChoropleth %}
- {% endblock choropleth_popup %}
+ // Create a popup
+ var popup{{ layerId }} = new mapboxgl.Popup({
+ closeButton: false,
+ closeOnClick: false
+ });
- map.on('mouseleave', 'choropleth-fill', function() {
- map.getCanvas().style.cursor = '';
- popup.remove();
- });
-
- // Fly to on click
- map.on('click', 'choropleth-fill', function(e) {
- map.flyTo({
- center: e.lngLat,
- zoom: map.getZoom() + 1
+ // Show the popup on mouseover
+ map.on('mousemove', "choropleth-extrusion{{ layerId }}", function(e) {
+ map.getCanvas().style.cursor = 'pointer';
+
+ let f = e.features[0];
+ let popup_html = '';
+
+ for (key in f.properties) {
+ popup_html += '
' + key + ': ' + f.properties[key] + ' '
+ }
+
+ popup_html += ''
+ popup{{ layerId }}.setLngLat(e.lngLat)
+ .setHTML(popup_html)
+ .addTo(map);
+ });
+ 9
+ map.on('mouseleave', "choropleth-extrusion{{ layerId }}", function() {
+ map.getCanvas().style.cursor = '';
+ popup{{ layerId }}.remove();
});
- });
- });
+ // Fly to on click
+ map.on('click', "choropleth-extrusion{{ layerId }}", function(e) {
+ map.flyTo({
+ center: e.lngLat,
+ zoom: map.getZoom() + 1
+ });
+ });
-{% endblock map %}
+ {% else %}
+
+ // Show the popup on mouseover
+ map.on('mousemove', "choropleth-fill{{ layerId }}", function(e) {
+ map.getCanvas().style.cursor = 'pointer';
+
+ let f = e.features[0];
+ let popup_html = '';
+
+ for (key in f.properties) {
+ popup_html += '
' + key + ': ' + f.properties[key] + ' '
+ }
+
+ popup_html += ''
+ popup{{ layerId }}.setLngLat(e.lngLat)
+ .setHTML(popup_html)
+ .addTo(map);
+ });
+
+
+ map.on('mouseleave', "choropleth-fill{{ layerId }}", function() {
+ map.getCanvas().style.cursor = '';
+ popup{{ layerId }}.remove();
+ });
+
+ // Fly to on click
+ map.on('click', "choropleth-fill{{ layerId }}", function(e) {
+ map.flyTo({
+ center: e.lngLat,
+ zoom: map.getZoom() + 1
+ });
+ });
+
+ {% endif %}
+
+ {% endblock choropleth_popup %}
+
+
+
+ });
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/circle.html b/mapboxgl/templates/circle.html
index c9be63f..5004e9b 100644
--- a/mapboxgl/templates/circle.html
+++ b/mapboxgl/templates/circle.html
@@ -1,27 +1,20 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
{% block extra_css %}
-
{% endblock extra_css %}
-{% block legend %}
-
+{% block extra_javascript %}
{% if showLegend %}
{% if colorStops and colorProperty %}
- calcColorLegend({{ colorStops }} , "{{ colorProperty }}");
+ calcColorLegend({{ colorStops }} , "{{ colorProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
{% endif %}
-
-{% endblock legend %}
-
-{% block map %}
map.on('style.load', function() {
-
- map.addSource("data", {
+
+ map.addSource("data{{ layerId }}", {
"type": "geojson",
"data": {{ geojson_data }},
"buffer": 1,
@@ -29,11 +22,11 @@
});
map.addLayer({
- "id": "label",
- "source": "data",
+ "id": "label{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "symbol",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"layout": {
{% if labelProperty %}
"text-field": "{{ labelProperty }}",
@@ -49,11 +42,11 @@
}, "{{belowLayer}}" )
map.addLayer({
- "id": "circle",
- "source": "data",
+ "id": "circle{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "circle",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"paint": {
{% if colorProperty %}
"circle-color": generatePropertyExpression("{{ colorType }}", "{{ colorProperty }}", {{ colorStops }}, "{{ defaultColor }}" ),
@@ -66,16 +59,16 @@
"circle-opacity" : {{ opacity }},
"circle-stroke-opacity" : {{ opacity }}
}
- }, "label");
+ }, "label{{ layerId }}");
// Create a popup
- var popup = new mapboxgl.Popup({
+ var popup{{ layerId }} = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
// Show the popup on mouseover
- map.on('mousemove', 'circle', function(e) {
+ map.on('mousemove', "circle{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
@@ -87,23 +80,22 @@
}
popup_html += ''
- popup.setLngLat(e.features[0].geometry.coordinates)
+ popup{{ layerId }}.setLngLat(e.features[0].geometry.coordinates)
.setHTML(popup_html)
.addTo(map);
});
- map.on('mouseleave', 'circle', function() {
+ map.on('mouseleave', "circle{{ layerId }}", function() {
map.getCanvas().style.cursor = '';
- popup.remove();
+ popup{{ layerId }}.remove();
});
// Fly to on click
- map.on('click', 'circle', function(e) {
+ map.on('click', "circle{{ layerId }}", function(e) {
map.easeTo({
center: e.features[0].geometry.coordinates,
zoom: map.getZoom() + 1
});
});
});
-
-{% endblock map %}
+{% endblock extra_javascript %}
\ No newline at end of file
diff --git a/mapboxgl/templates/clustered_circle.html b/mapboxgl/templates/clustered_circle.html
index 5a6717e..364c17f 100644
--- a/mapboxgl/templates/clustered_circle.html
+++ b/mapboxgl/templates/clustered_circle.html
@@ -1,25 +1,19 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
{% block extra_css %}
-
{% endblock extra_css %}
-{% block legend %}
+{% block extra_javascript %}
{% if showLegend %}
- calcColorLegend({{ colorStops }}, "Point Density");
+ calcColorLegend({{ colorStops }}, "Point Density", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
-{% endblock legend %}
-
-{% block map %}
-
map.on('style.load', function() {
-
- map.addSource("data", {
+
+ map.addSource("data{{ layerId }}", {
"type": "geojson",
"data": {{ geojson_data }}, //data from dataframe output to geojson
"buffer": 0,
@@ -30,11 +24,11 @@
});
map.addLayer({
- "id": "label",
- "source": "data",
+ "id": "label{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "symbol",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"layout": {
"text-field": "{point_count_abbreviated}",
"text-size" : generateInterpolateExpression('zoom', [[0, {{ labelSize }}],[22, 3* {{ labelSize }}]] ),
@@ -47,11 +41,11 @@
}, "{{belowLayer}}" )
map.addLayer({
- "id": "circle-cluster",
- "source": "data",
+ "id": "circle-cluster{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "circle",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"filter": ["has", "point_count"],
"paint": {
"circle-color": generateInterpolateExpression( "point_count", {{ colorStops }} ),
@@ -61,14 +55,14 @@
"circle-opacity" : {{ opacity }},
"circle-stroke-opacity" : {{ opacity }}
}
- }, "label");
+ }, "label{{ layerId }}");
map.addLayer({
- "id": "circle-unclustered",
- "source": "data",
+ "id": "circle-unclustered{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "circle",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"filter": ["!has", "point_count"],
"paint": {
"circle-color": "{{ colorDefault }}",
@@ -78,16 +72,16 @@
"circle-opacity" : {{ opacity }},
"circle-stroke-opacity" : {{ opacity }}
}
- }, "circle-cluster");
+ }, "circle-cluster{{ layerId }}");
// Create a popup
- var popup = new mapboxgl.Popup({
+ var popup{{ layerId }} = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
// Show the popup on mouseover
- map.on('mousemove', 'circle-unclustered', function(e) {
+ map.on('mousemove', "circle-unclustered{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
let popup_html = 'Location: ' + f.geometry.coordinates[0].toPrecision(6) +
@@ -99,12 +93,12 @@
popup_html += ''
- popup.setLngLat(e.features[0].geometry.coordinates)
+ popup{{ layerId }}.setLngLat(e.features[0].geometry.coordinates)
.setHTML(popup_html)
.addTo(map);
});
- map.on('mousemove', 'circle-cluster', function(e) {
+ map.on('mousemove', "circle-cluster{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
let popup_html = 'Location: ' + f.geometry.coordinates[0].toPrecision(6) +
@@ -114,30 +108,30 @@
popup_html += ''
- popup.setLngLat(e.features[0].geometry.coordinates)
+ popup{{ layerId }}.setLngLat(e.features[0].geometry.coordinates)
.setHTML(popup_html)
.addTo(map);
});
- map.on('mouseleave', 'circle-unclustered', function() {
+ map.on('mouseleave', "circle-unclustered{{ layerId }}", function() {
map.getCanvas().style.cursor = '';
- popup.remove();
+ popup{{ layerId }}.remove();
});
- map.on('mouseleave', 'circle-cluster', function() {
+ map.on('mouseleave', "circle-cluster{{ layerId }}", function() {
map.getCanvas().style.cursor = '';
- popup.remove();
+ popup{{ layerId }}.remove();
});
- map.on('click', 'circle-unclustered', function(e) {
+ map.on('click', "circle-unclustered{{ layerId }}", function(e) {
map.easeTo({
center: e.features[0].geometry.coordinates,
zoom: map.getZoom() + 1
});
});
- map.on('click', 'circle-cluster', function(e) {
+ map.on('click', "circle-cluster{{ layerId }}", function(e) {
map.easeTo({
center: e.features[0].geometry.coordinates,
zoom: map.getZoom() + 1
@@ -145,4 +139,4 @@
});
});
-{% endblock map %}
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/graduated_circle.html b/mapboxgl/templates/graduated_circle.html
index b95cb40..4d44324 100644
--- a/mapboxgl/templates/graduated_circle.html
+++ b/mapboxgl/templates/graduated_circle.html
@@ -1,27 +1,21 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
{% block extra_css %}
-
{% endblock extra_css %}
-{% block legend %}
+{% block extra_javascript %}
{% if showLegend %}
{% if colorStops and colorProperty and radiusProperty %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }} vs. {{ radiusProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }} vs. {{ radiusProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
{% endif %}
-{% endblock legend %}
-
-{% block map %}
-
map.on('style.load', function() {
- map.addSource("data", {
+ map.addSource("data{{ layerId }}", {
"type": "geojson",
"data": {{ geojson_data }}, //data from dataframe output to geojson
"buffer": 0,
@@ -29,11 +23,11 @@
});
map.addLayer({
- "id": "label",
- "source": "data",
+ "id": "label{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "symbol",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"layout": {
{% if labelProperty %}
"text-field": "{{ labelProperty }}",
@@ -49,11 +43,11 @@
}, "{{belowLayer}}" )
map.addLayer({
- "id": "circle",
- "source": "data",
+ "id": "circle{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "circle",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"paint": {
{% if colorProperty %}
"circle-color": generatePropertyExpression("{{ colorType }}", "{{ colorProperty }}", {{ colorStops }}, "{{ defaultColor }}" ),
@@ -70,16 +64,16 @@
"circle-opacity" : {{ opacity }},
"circle-stroke-opacity" : {{ opacity }}
}
- }, "label");
+ }, "label{{ layerId }}");
// Create a popup
- var popup = new mapboxgl.Popup({
+ var popup{{ layerId }} = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
// Show the popup on mouseover
- map.on('mousemove', 'circle', function(e) {
+ map.on('mousemove', "circle{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
let popup_html = 'Location: ' + f.geometry.coordinates[0].toPrecision(6) +
@@ -91,18 +85,18 @@
popup_html += ''
- popup.setLngLat(e.features[0].geometry.coordinates)
+ popup{{ layerId }}.setLngLat(e.features[0].geometry.coordinates)
.setHTML(popup_html)
.addTo(map);
});
- map.on('mouseleave', 'circle', function() {
+ map.on('mouseleave', "circle{{ layerId }}", function() {
map.getCanvas().style.cursor = '';
- popup.remove();
+ popup{{ layerId }}.remove();
});
// Fly to on click
- map.on('click', 'circle', function(e) {
+ map.on('click', "circle{{ layerId }}", function(e) {
map.easeTo({
center: e.features[0].geometry.coordinates,
zoom: map.getZoom() + 1
@@ -110,4 +104,4 @@
});
});
-{% endblock map %}
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/heatmap.html b/mapboxgl/templates/heatmap.html
index 2d3dd90..86838a2 100644
--- a/mapboxgl/templates/heatmap.html
+++ b/mapboxgl/templates/heatmap.html
@@ -1,12 +1,10 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
-{% block legend %}{% endblock legend %}
-
-{% block map %}
+{% block extra_javascript %}
map.on('style.load', function() {
- map.addSource("data", {
+ map.addSource("data{{ layerId }}", {
"type": "geojson",
"data": {{ geojson_data }}, //data from dataframe output to geojson
"buffer": 0,
@@ -14,11 +12,11 @@
});
map.addLayer({
- "id": "circle",
- "source": "data",
+ "id": "circle{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "heatmap",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
+ "maxzoom": {{ maxZoom }},
+ "minzoom": {{ minZoom }},
"paint": {
{% if radiusStops %}
"heatmap-radius": generatePropertyExpression('interpolate', 'zoom', {{ radiusStops }}),
@@ -38,4 +36,4 @@
});
-{% endblock map %}
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/image.html b/mapboxgl/templates/image.html
index 194f356..e4fffeb 100644
--- a/mapboxgl/templates/image.html
+++ b/mapboxgl/templates/image.html
@@ -1,23 +1,21 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
-{% block legend %}{% endblock legend %}
-
-{% block map %}
+{% block extra_javascript %}
map.on('style.load', function() {
- map.addSource("image", {
+ map.addSource("data{{ "image{{ layerId }}" }}", {
"type": "image",
"url": "{{ image }}", // url or base64 encoded image
"coordinates": {{ coordinates }}
});
map.addLayer({
- "id": 'image',
+ "id": "image{{ layerId }}",
"type": "raster",
- "source": "image"
+ "source": "data{{ "image{{ layerId }}" }}"
}, "{{belowLayer}}");
});
-{% endblock map %}
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/layer.html b/mapboxgl/templates/layer.html
new file mode 100644
index 0000000..0c30e63
--- /dev/null
+++ b/mapboxgl/templates/layer.html
@@ -0,0 +1,14 @@
+
+
+
+
+
diff --git a/mapboxgl/templates/linestring.html b/mapboxgl/templates/linestring.html
index 760b273..0be674a 100644
--- a/mapboxgl/templates/linestring.html
+++ b/mapboxgl/templates/linestring.html
@@ -1,29 +1,25 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
-{% block legend %}
+{% block extra_javascript %}
{% if showLegend %}
{% if colorStops and colorProperty and widthProperty %}
{% if colorProperty != widthProperty %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }} vs. {{ widthProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }} vs. {{ widthProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% else %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
{% elif colorStops and colorProperty %}
- calcColorLegend({{ colorStops }}, "{{ colorProperty }}");
+ calcColorLegend({{ colorStops }}, "{{ colorProperty }}", {{ layerId }}, "{{legendKeyShape}}", {{legendGradient}}, {{legendNumericPrecision}}, "{{legendLayout}}" );
{% endif %}
{% endif %}
-{% endblock legend %}
-
-{% block map %}
-
map.on('style.load', function() {
{% block linestring %}
// Add geojson data source
- map.addSource("data", {
+ map.addSource("data{{ layerId }}", {
"type": "geojson",
"data": {{ geojson_data }},
"buffer": 1,
@@ -32,8 +28,8 @@
// Add data layer
map.addLayer({
- "id": "linestring",
- "source": "data",
+ "id": "linestring{{ layerId }}",
+ "source": "data{{ layerId }}",
"type": "line",
"layout": {
"line-join": "round",
@@ -59,8 +55,8 @@
// Add label layer
map.addLayer({
- "id": "linestring-label",
- "source": "data",
+ "id": "label{{ layerId }}",
+ "source": "data{{ layerId }}"",
"type": "symbol",
"layout": {
{% if labelProperty %}
@@ -77,17 +73,17 @@
}, "{{belowLayer}}" );
{% endblock linestring %}
+
+ {% block linestring_popup %}
// Create a popup
- var popup = new mapboxgl.Popup({
+ var popup{{ layerId }} = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
});
-
- {% block linestring_popup %}
// Show the popup on mouseover
- map.on('mousemove', 'linestring', function(e) {
+ map.on('mousemove', "linestring{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
@@ -98,20 +94,20 @@
}
popup_html += ''
- popup.setLngLat(e.lngLat)
+ popup{{ layerId }}.setLngLat(e.lngLat)
.setHTML(popup_html)
.addTo(map);
});
{% endblock linestring_popup %}
- map.on('mouseleave', 'linestring', function() {
+ map.on('mouseleave', "linestring{{ layerId }}", function() {
map.getCanvas().style.cursor = '';
- popup.remove();
+ popup{{ layerId }}.remove();
});
// Fly to on click
- map.on('click', 'linestring', function(e) {
+ map.on('click', "linestring{{ layerId }}", function(e) {
map.flyTo({
center: e.lngLat,
zoom: map.getZoom() + 1
@@ -120,4 +116,4 @@
});
-{% endblock map %}
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/main.html b/mapboxgl/templates/main.html
deleted file mode 100644
index dd240d9..0000000
--- a/mapboxgl/templates/main.html
+++ /dev/null
@@ -1,256 +0,0 @@
-
-
-
-mapboxgl-jupyter viz
-
-
-
-
-
-
-{% block extra_css %}{% endblock extra_css %}
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/mapboxgl/templates/map.html b/mapboxgl/templates/map.html
index 5f5c340..d9b56ba 100644
--- a/mapboxgl/templates/map.html
+++ b/mapboxgl/templates/map.html
@@ -1,29 +1,302 @@
-{% extends "base.html" %}
-
-{% block legend %}{% endblock legend %}
-
-{% block map %}
-
- map.on('style.load', function() {
-
- // Add data source
- map.addSource("data", {
- "type": "geojson",
- "data": {{ geojson_data }},
- "buffer": 1,
- "maxzoom": 14
- });
-
- // Add data layer
- map.addLayer({
- "id": "circle",
- "source": "data",
- "type": "circle",
- "maxzoom": {{ maxzoom }},
- "minzoom": {{ minzoom }},
- "paint": { "circle-radius": 1 }
- });
+
+
+
+mapboxgl-jupyter viz
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/mapboxgl/templates/raster.html b/mapboxgl/templates/raster.html
index 1561ed1..670c308 100644
--- a/mapboxgl/templates/raster.html
+++ b/mapboxgl/templates/raster.html
@@ -1,11 +1,8 @@
-{% extends "base.html" %}
+{% extends "layer.html" %}
-{% block legend %}{% endblock legend %}
-
-{% block map %}
+{% block extra_javascript %}
map.on('style.load', function() {
- console.log('Yoooooo');
let params = {
"type": "raster",
"tiles": [
@@ -17,14 +14,14 @@
}
if ({{ tiles_bounds }} !== undefined) params.bounds = {{ tiles_bounds }};
- map.addSource("raster-tiles", params);
+ map.addSource("raster-tiles{{ "rater-layer{{ layerId }}" }}", params);
map.addLayer({
- "id": "raster-tiles",
+ "id": "rater-layer{{ layerId }}",
"type": "raster",
- "source": "raster-tiles"
+ "source": "raster-tiles{{ "rater-layer{{ layerId }}" }}",
}, "{{belowLayer}}" );
});
-{% endblock map %}
+{% endblock extra_javascript %}
diff --git a/mapboxgl/templates/vector_choropleth.html b/mapboxgl/templates/vector_choropleth.html
index 139b913..d22b119 100644
--- a/mapboxgl/templates/vector_choropleth.html
+++ b/mapboxgl/templates/vector_choropleth.html
@@ -27,16 +27,16 @@
{% endif %}
// Add vector data source
- map.addSource("vector-data", {
+ map.addSource("data{{ layerId }}", {
type: "vector",
url: "{{ vectorUrl }}",
});
// Add layer from the vector tile source with data-driven style
map.addLayer({
- "id": "choropleth-fill",
+ "id": "choropleth-fill{{ layerId }}",
"type": "fill",
- "source": "vector-data",
+ "source": "data{{ layerId }}",
"source-layer": "{{ vectorLayer }}",
"paint": {
"fill-color": {
@@ -52,8 +52,8 @@
// Add border layer
map.addLayer({
- "id": "choropleth-line",
- "source": "vector-data",
+ "id": "choropleth-line{{ layerId }}",
+ "source": "data{{ layerId }}",
"source-layer": "{{ vectorLayer }}",
"type": "line",
"layout": {
@@ -73,8 +73,8 @@
// Add label layer
map.addLayer({
- "id": "choropleth-label",
- "source": "vector-data",
+ "id": "choropleth-label{{ layerId }}",
+ "source": "data{{ layerId }}",
"source-layer": "{{ vectorLayer }}",
"type": "symbol",
"layout": {
@@ -95,9 +95,9 @@
{% if extrudeChoropleth %}
map.addLayer({
- id: "choropleth-extrusion",
+ id: "choropleth-extrusion{{ layerId }}",
type: "fill-extrusion",
- "source": "vector-data",
+ "source": "data{{ layerId }}",
"source-layer": "{{ vectorLayer }}",
paint: {
"fill-extrusion-opacity": {{ opacity }},
@@ -123,8 +123,14 @@
{% block choropleth_popup %}
+ // Create a popup
+ var popup{{ layerId }} = new mapboxgl.Popup({
+ closeButton: false,
+ closeOnClick: false
+ });
+
// Show the popup on mouseover
- map.on('mousemove', 'choropleth-fill', function(e) {
+ map.on('mousemove', "choropleth-fill{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
@@ -144,7 +150,7 @@
{% endif %}
popup_html += ''
- popup.setLngLat(e.lngLat)
+ popup{{ layerId }}.setLngLat(e.lngLat)
.setHTML(popup_html)
.addTo(map);
});
diff --git a/mapboxgl/templates/vector_linestring.html b/mapboxgl/templates/vector_linestring.html
index 9b099f0..4777c8c 100644
--- a/mapboxgl/templates/vector_linestring.html
+++ b/mapboxgl/templates/vector_linestring.html
@@ -30,15 +30,15 @@
{% endif %}
// Add vector data source
- map.addSource("vector-data", {
+ map.addSource("data{{ layerId }}", {
type: "vector",
url: "{{ vectorUrl }}",
});
// Add data layer from the vector tile source with data-driven style
map.addLayer({
- "id": "linestring",
- "source": "vector-data",
+ "id": "linestring{{ layerId }}",
+ "source": "data{{ layerId }}",
"source-layer": "{{ vectorLayer }}",
"type": "line",
"layout": {
@@ -69,8 +69,8 @@
// Add label layer
map.addLayer({
- "id": "linestring-label",
- "source": "vector-data",
+ "id": "label{{ layerId }}",
+ "source": "data{{ layerId }}",
"source-layer": "{{ vectorLayer }}",
"type": "symbol",
"layout": {
@@ -92,8 +92,14 @@
{% block linestring_popup %}
+ // Create a popup
+ var popup{{ layerId }} = new mapboxgl.Popup({
+ closeButton: false,
+ closeOnClick: false
+ });
+
// Show the popup on mouseover
- map.on('mousemove', 'linestring', function(e) {
+ map.on('mousemove', "linestring{{ layerId }}", function(e) {
map.getCanvas().style.cursor = 'pointer';
let f = e.features[0];
@@ -115,7 +121,7 @@
{% endif %}
popup_html += ''
- popup.setLngLat(e.lngLat)
+ popup{{ layerId }}.setLngLat(e.lngLat)
.setHTML(popup_html)
.addTo(map);
});
diff --git a/mapboxgl/viz.py b/mapboxgl/viz.py
index 87d6418..8fca800 100644
--- a/mapboxgl/viz.py
+++ b/mapboxgl/viz.py
@@ -1,20 +1,17 @@
-import codecs
-import json
-import os
+from .map import Map
+from .layers import (
+ CircleLayer,
+ GraduatedCircleLayer,
+ HeatmapLayer,
+ ClusteredCircleLayer,
+ ImageLayer,
+ RasterTilesLayer,
+ ChoroplethLayer,
+ LinestringLayer,
+)
from IPython.core.display import HTML, display
-import numpy
-
-from mapboxgl.errors import TokenError
-from mapboxgl.utils import color_map, height_map
-from mapboxgl import templates
-from mapboxgl.utils import img_encode, numeric_map
-
-
-GL_JS_VERSION = 'v0.49.0'
-
-
class MapViz(object):
def __init__(self,
@@ -28,8 +25,6 @@ def __init__(self,
style='mapbox://styles/mapbox/light-v9?optimize=true',
width='100%',
zoom=0,
- min_zoom=0,
- max_zoom=24,
pitch=0,
bearing=0,
box_zoom_on=True,
@@ -77,116 +72,51 @@ def __init__(self,
:param legend_key_borders_on: boolean for whether to show/hide legend key borders
"""
- if access_token is None:
- access_token = os.environ.get('MAPBOX_ACCESS_TOKEN', '')
- if access_token.startswith('sk'):
- raise TokenError('Mapbox access token must be public (pk), not secret (sk). ' \
- 'Please sign up at https://www.mapbox.com/signup/ to get a public token. ' \
- 'If you already have an account, you can retreive your token at https://www.mapbox.com/account/.')
- self.access_token = access_token
- self.template = 'map'
- self.data = data
- self.div_id = div_id
- self.width = width
- self.height = height
- self.style = style
- self.center = center
- self.zoom = zoom
- self.below_layer = below_layer
- self.opacity = opacity
- self.label_property = None
- self.min_zoom = min_zoom
- self.max_zoom = max_zoom
- self.pitch = pitch
- self.bearing = bearing
- self.box_zoom_on = box_zoom_on
- self.double_click_zoom_on = double_click_zoom_on
- self.scroll_zoom_on = scroll_zoom_on
- self.touch_zoom_on = touch_zoom_on
- self.legend = legend
- self.legend_layout = legend_layout
- self.legend_style = legend_style
- self.legend_gradient = legend_gradient
- self.legend_fill = legend_fill
- self.legend_header_fill = legend_header_fill
- self.legend_text_color = legend_text_color
- self.legend_text_numeric_precision = legend_text_numeric_precision
- self.legend_title_halo_color = legend_title_halo_color
- self.legend_key_shape = legend_key_shape
- self.legend_key_borders_on = legend_key_borders_on
+ self.__dict__['map'] = Map(
+ access_token=access_token,
+ center=center,
+ opacity=opacity,
+ div_id=div_id,
+ height=height,
+ style=style,
+ width=width,
+ zoom=zoom,
+ pitch=pitch,
+ bearing=bearing,
+ box_zoom_on=box_zoom_on,
+ double_click_zoom_on=double_click_zoom_on,
+ scroll_zoom_on=scroll_zoom_on,
+ touch_zoom_on=touch_zoom_on,
+ legend_fill=legend_fill,
+ legend_header_fill=legend_header_fill,
+ legend_text_color=legend_text_color,
+ legend_title_halo_color=legend_title_halo_color,
+ legend_key_borders_on=legend_key_borders_on
+ )
+
+ self.__dict__['layer'] = None
+
+ def __setattr__(self, name, value):
+ if hasattr(self.map, name):
+ self.map.__dict__[name] = value
+ elif hasattr(self.layer, name):
+ self.layer.__dict__[name] = value
+
def as_iframe(self, html_data):
"""Build the HTML representation for the mapviz."""
-
- srcdoc = html_data.replace('"', "'")
- return (''.format(
- div_id=self.div_id,
- srcdoc=srcdoc,
- width=self.width,
- height=self.height))
+ return self.map.as_iframe(html_data)
def show(self, **kwargs):
# Load the HTML iframe
- html = self.create_html(**kwargs)
- map_html = self.as_iframe(html)
-
- # Display the iframe in the current jupyter notebook view
- display(HTML(map_html))
+ self.map.show(**kwargs)
def add_unique_template_variables(self, options):
pass
def create_html(self, filename=None):
"""Create a circle visual from a geojson data source"""
- if isinstance(self.style, str):
- style = "'{}'".format(self.style)
- else:
- style = self.style
- options = dict(
- gl_js_version=GL_JS_VERSION,
- accessToken=self.access_token,
- div_id=self.div_id,
- style=style,
- center=list(self.center),
- zoom=self.zoom,
- geojson_data=json.dumps(self.data, ensure_ascii=False),
- belowLayer=self.below_layer,
- opacity=self.opacity,
- minzoom=self.min_zoom,
- maxzoom=self.max_zoom,
- pitch=self.pitch,
- bearing=self.bearing,
- boxZoomOn=json.dumps(self.box_zoom_on),
- doubleClickZoomOn=json.dumps(self.double_click_zoom_on),
- scrollZoomOn=json.dumps(self.scroll_zoom_on),
- touchZoomOn=json.dumps(self.touch_zoom_on),
- showLegend=self.legend,
- legendLayout=self.legend_layout,
- legendStyle=self.legend_style, # reserve name for custom CSS?
- legendGradient=json.dumps(self.legend_gradient),
- legendFill=self.legend_fill,
- legendHeaderFill=self.legend_header_fill,
- legendTextColor=self.legend_text_color,
- legendNumericPrecision=json.dumps(self.legend_text_numeric_precision),
- legendTitleHaloColor=self.legend_title_halo_color,
- legendKeyShape=self.legend_key_shape,
- legendKeyBordersOn=json.dumps(self.legend_key_borders_on))
-
- if self.label_property is None:
- options.update(labelProperty=None)
- else:
- options.update(labelProperty='{' + self.label_property + '}')
-
- self.add_unique_template_variables(options)
-
- if filename:
- html = templates.format(self.template, **options)
- with codecs.open(filename, "w", "utf-8-sig") as f:
- f.write(html)
- return None
- else:
- return templates.format(self.template, **options)
+ return self.map.create_html(filename)
class CircleViz(MapViz):
@@ -207,6 +137,8 @@ def __init__(self,
stroke_color='grey',
stroke_width=0.1,
legend_key_shape='circle',
+ min_zoom=0,
+ max_zoom=24,
*args,
**kwargs):
"""Construct a Mapviz object
@@ -227,37 +159,28 @@ def __init__(self,
"""
super(CircleViz, self).__init__(data, *args, **kwargs)
- self.template = 'circle'
- self.label_property = label_property
- self.label_color = label_color
- self.label_size = label_size
- self.label_halo_color = label_halo_color
- self.label_halo_width = label_halo_width
- self.color_property = color_property
- self.color_stops = color_stops
- self.radius = radius
- self.stroke_color = stroke_color
- self.stroke_width = stroke_width
- self.color_function_type = color_function_type
- self.color_default = color_default
- self.legend_key_shape = legend_key_shape
-
- def add_unique_template_variables(self, options):
- """Update map template variables specific to circle visual"""
- options.update(dict(
- geojson_data=json.dumps(self.data, ensure_ascii=False),
- colorProperty=self.color_property,
- colorType=self.color_function_type,
- colorStops=self.color_stops,
- strokeWidth=self.stroke_width,
- strokeColor=self.stroke_color,
- radius=self.radius,
- defaultColor=self.color_default,
- labelColor=self.label_color,
- labelSize=self.label_size,
- labelHaloColor=self.label_halo_color,
- labelHaloWidth=self.label_halo_width,
- ))
+ self.__dict__['layer'] = CircleLayer(
+ data=data,
+ label_property=label_property,
+ label_color=label_color,
+ label_size=label_size,
+ label_halo_color=label_halo_color,
+ label_halo_width=label_halo_width,
+ color_property=color_property,
+ color_stops=color_stops,
+ radius=radius,
+ stroke_color=stroke_color,
+ stroke_width=stroke_width,
+ color_function_type=color_function_type,
+ color_default=color_default,
+ legend_key_shape=legend_key_shape,
+ min_zoom=min_zoom,
+ max_zoom=max_zoom,
+ *args,
+ **kwargs
+ )
+
+ self.map.add_layer(self.layer)
class GraduatedCircleViz(MapViz):
@@ -300,42 +223,29 @@ def __init__(self,
"""
super(GraduatedCircleViz, self).__init__(data, *args, **kwargs)
- self.template = 'graduated_circle'
- self.label_property = label_property
- self.label_color = label_color
- self.label_size = label_size
- self.label_halo_color = label_halo_color
- self.label_halo_width = label_halo_width
- self.color_property = color_property
- self.color_stops = color_stops
- self.radius_property = radius_property
- self.radius_stops = radius_stops
- self.color_function_type = color_function_type
- self.color_default = color_default
- self.radius_function_type = radius_function_type
- self.radius_default = radius_default
- self.stroke_color = stroke_color
- self.stroke_width = stroke_width
- self.legend_key_shape = legend_key_shape
-
- def add_unique_template_variables(self, options):
- """Update map template variables specific to graduated circle visual"""
- options.update(dict(
- colorProperty=self.color_property,
- colorStops=self.color_stops,
- colorType=self.color_function_type,
- radiusType=self.radius_function_type,
- defaultColor=self.color_default,
- defaultRadius=self.radius_default,
- radiusProperty=self.radius_property,
- radiusStops=self.radius_stops,
- strokeWidth=self.stroke_width,
- strokeColor=self.stroke_color,
- labelColor=self.label_color,
- labelSize=self.label_size,
- labelHaloColor=self.label_halo_color,
- labelHaloWidth=self.label_halo_width
- ))
+ self.__dict__['layer'] = GraduatedCircleLayer(
+ data=data,
+ label_property=label_property,
+ label_color=label_color,
+ label_size=label_size,
+ label_halo_color=label_halo_color,
+ label_halo_width=label_halo_width,
+ color_property=color_property,
+ color_stops=color_stops,
+ radius_property=radius_property,
+ radius_stops=radius_stops,
+ color_function_type=color_function_type,
+ color_default=color_default,
+ radius_function_type=radius_function_type,
+ radius_default=radius_default,
+ stroke_color=stroke_color,
+ stroke_width=stroke_width,
+ legend_key_shape=legend_key_shape,
+ *args,
+ **kwargs
+ )
+
+ self.map.add_layer(self.layer)
class HeatmapViz(MapViz):
@@ -343,6 +253,7 @@ class HeatmapViz(MapViz):
def __init__(self,
data,
+ label_property=None,
weight_property=None,
weight_stops=None,
color_stops=None,
@@ -361,24 +272,19 @@ def __init__(self,
"""
super(HeatmapViz, self).__init__(data, *args, **kwargs)
- self.template = 'heatmap'
- self.weight_property = weight_property
- self.weight_stops = weight_stops
- if color_stops:
- # Make the first color stop in a heatmap have opacity 0 for good visual effect
- self.color_stops = [[0.00001, 'rgba(0,0,0,0)']] + color_stops
- self.radius_stops = radius_stops
- self.intensity_stops = intensity_stops
+ self.__dict__['layer'] = HeatmapLayer(
+ data=data,
+ label_property=label_property,
+ weight_property=weight_property,
+ weight_stops=weight_stops,
+ color_stops=color_stops,
+ radius_stops=radius_stops,
+ intensity_stops=intensity_stops,
+ *args,
+ **kwargs
+ )
- def add_unique_template_variables(self, options):
- """Update map template variables specific to heatmap visual"""
- options.update(dict(
- colorStops=self.color_stops,
- radiusStops=self.radius_stops,
- weightProperty=self.weight_property,
- weightStops=self.weight_stops,
- intensityStops=self.intensity_stops,
- ))
+ self.map.add_layer(self.layer)
class ClusteredCircleViz(MapViz):
@@ -415,38 +321,26 @@ def __init__(self,
"""
super(ClusteredCircleViz, self).__init__(data, *args, **kwargs)
- self.template = 'clustered_circle'
- self.label_color = label_color
- self.label_size = label_size
- self.label_halo_color = label_halo_color
- self.label_halo_width = label_halo_width
- self.color_stops = color_stops
- self.radius_stops = radius_stops
- self.clusterRadius = cluster_radius
- self.clusterMaxZoom = cluster_maxzoom
- self.radius_default = radius_default
- self.color_default = color_default
- self.stroke_color = stroke_color
- self.stroke_width = stroke_width
- self.color_default = color_default
- self.legend_key_shape = legend_key_shape
-
- def add_unique_template_variables(self, options):
- """Update map template variables specific to a clustered circle visual"""
- options.update(dict(
- colorStops=self.color_stops,
- colorDefault=self.color_default,
- radiusStops=self.radius_stops,
- clusterRadius=self.clusterRadius,
- clusterMaxZoom=self.clusterMaxZoom,
- strokeWidth=self.stroke_width,
- strokeColor=self.stroke_color,
- radiusDefault=self.radius_default,
- labelColor=self.label_color,
- labelSize=self.label_size,
- labelHaloColor=self.label_halo_color,
- labelHaloWidth=self.label_halo_width
- ))
+ self.__dict__['layer'] = ClusteredCircleLayer(
+ data=data,
+ label_color=label_color,
+ label_size=label_size,
+ label_halo_color=label_halo_color,
+ label_halo_width=label_halo_width,
+ color_stops=color_stops,
+ radius_stops=radius_stops,
+ cluster_radius=cluster_radius,
+ cluster_maxzoom=cluster_maxzoom,
+ radius_default=radius_default,
+ color_default=color_default,
+ stroke_color=stroke_color,
+ stroke_width=stroke_width,
+ legend_key_shape=legend_key_shape,
+ *args,
+ **kwargs
+ )
+
+ self.map.add_layer(self.layer)
class ChoroplethViz(MapViz):
@@ -466,7 +360,7 @@ def __init__(self,
line_color='white',
line_stroke='solid',
line_width=1,
- height_property=None,
+ height_property=None,
height_stops=None,
height_default=0.0,
height_function_type='interpolate',
@@ -495,122 +389,39 @@ def __init__(self,
"""
super(ChoroplethViz, self).__init__(data, *args, **kwargs)
-
- self.vector_url = vector_url
- self.vector_layer_name = vector_layer_name
- self.vector_join_property = vector_join_property
- self.data_join_property = data_join_property
-
- if self.vector_url is not None and self.vector_layer_name is not None:
- self.template = 'vector_choropleth'
- self.vector_source = True
- else:
- self.vector_source = False
- self.template = 'choropleth'
-
- self.label_property = label_property
- self.color_property = color_property
- self.color_stops = color_stops
- self.color_default = color_default
- self.color_function_type = color_function_type
- self.line_color = line_color
- self.line_stroke = line_stroke
- self.line_width = line_width
- self.height_property = height_property
- self.height_stops = height_stops
- self.height_default = height_default
- self.height_function_type = height_function_type
- self.legend_key_shape = legend_key_shape
+
+ self.__dict__['layer'] = ChoroplethLayer(
+ data=data,
+ vector_url=vector_url,
+ vector_layer_name=vector_layer_name,
+ vector_join_property=vector_join_property,
+ data_join_property=data_join_property, # vector only
+ label_property=label_property,
+ color_property=color_property,
+ color_stops=color_stops,
+ color_default=color_default,
+ color_function_type=color_function_type,
+ line_color=line_color,
+ line_stroke=line_stroke,
+ line_width=line_width,
+ height_property=height_property,
+ height_stops=height_stops,
+ height_default=height_default,
+ height_function_type=height_function_type,
+ legend_key_shape=legend_key_shape,
+ *args,
+ **kwargs
+ )
+
+ self.map.add_layer(self.layer)
def generate_vector_color_map(self):
"""Generate color stops array for use with match expression in mapbox template"""
- vector_stops = []
- for row in self.data:
-
- # map color to JSON feature using color_property
- color = color_map(row[self.color_property], self.color_stops, self.color_default)
-
- # link to vector feature using data_join_property (from JSON object)
- vector_stops.append([row[self.data_join_property], color])
-
- return vector_stops
+ return self.layer.generate_vector_color_map()
def generate_vector_height_map(self):
"""Generate height stops array for use with match expression in mapbox template"""
- vector_stops = []
-
- if self.height_function_type == 'match':
- match_height = self.height_stops
-
- for row in self.data:
-
- # map height to JSON feature using height_property
- height = height_map(row[self.height_property], self.height_stops, self.height_default)
-
- # link to vector feature using data_join_property (from JSON object)
- vector_stops.append([row[self.data_join_property], height])
-
- return vector_stops
-
- def add_unique_template_variables(self, options):
- """Update map template variables specific to heatmap visual"""
-
- # set line stroke dash interval based on line_stroke property
- if self.line_stroke in ["dashed", "--"]:
- self.line_dash_array = [6, 4]
- elif self.line_stroke in ["dotted", ":"]:
- self.line_dash_array = [0.5, 4]
- elif self.line_stroke in ["dash dot", "-."]:
- self.line_dash_array = [6, 4, 0.5, 4]
- elif self.line_stroke in ["solid", "-"]:
- self.line_dash_array = [1, 0]
- else:
- # default to solid line
- self.line_dash_array = [1, 0]
-
- # check if choropleth map should include 3-D extrusion
- self.extrude = all([bool(self.height_property), bool(self.height_stops)])
-
- # common variables for vector and geojson-based choropleths
- options.update(dict(
- colorStops=self.color_stops,
- colorProperty=self.color_property,
- colorType=self.color_function_type,
- defaultColor=self.color_default,
- lineColor=self.line_color,
- lineDashArray=self.line_dash_array,
- lineStroke=self.line_stroke,
- lineWidth=self.line_width,
- extrudeChoropleth=self.extrude,
- ))
- if self.extrude:
- options.update(dict(
- heightType=self.height_function_type,
- heightProperty=self.height_property,
- heightStops=self.height_stops,
- defaultHeight=self.height_default,
- ))
-
- # vector-based choropleth map variables
- if self.vector_source:
- options.update(dict(
- vectorUrl=self.vector_url,
- vectorLayer=self.vector_layer_name,
- vectorColorStops=self.generate_vector_color_map(),
- vectorJoinDataProperty=self.vector_join_property,
- joinData=json.dumps(self.data, ensure_ascii=False),
- dataJoinProperty=self.data_join_property,
- ))
- if self.extrude:
- options.update(dict(
- vectorHeightStops=self.generate_vector_height_map(),
- ))
-
- # geojson-based choropleth map variables
- else:
- options.update(dict(
- geojson_data=json.dumps(self.data, ensure_ascii=False),
- ))
+ return self.layer.generate_vector_height_map()
class ImageViz(MapViz):
@@ -632,18 +443,12 @@ def __init__(self,
"""
super(ImageViz, self).__init__(None, *args, **kwargs)
- if type(image) is numpy.ndarray:
- image = img_encode(image)
-
- self.template = 'image'
- self.image = image
- self.coordinates = coordinates
+ self.__dict__['layer'] = ImageLayer(
+ image=image,
+ coordinates=coordinates,
+ )
- def add_unique_template_variables(self, options):
- """Update map template variables specific to image visual"""
- options.update(dict(
- image=self.image,
- coordinates=self.coordinates))
+ self.map.add_layer(self.layer)
class RasterTilesViz(MapViz):
@@ -670,21 +475,15 @@ def __init__(self,
"""
super(RasterTilesViz, self).__init__(None, *args, **kwargs)
- self.template = 'raster'
- self.tiles_url = tiles_url
- self.tiles_size = tiles_size
- self.tiles_bounds = tiles_bounds
- self.tiles_minzoom = tiles_minzoom
- self.tiles_maxzoom = tiles_maxzoom
+ self.__dict__['layer'] = RasterTilesLayer(
+ tiles_url=tiles_url,
+ tiles_size=tiles_size,
+ tiles_bounds=tiles_bounds,
+ tiles_minzoom=tiles_minzoom,
+ tiles_maxzoom=tiles_maxzoom
+ )
- def add_unique_template_variables(self, options):
- """Update map template variables specific to a raster visual"""
- options.update(dict(
- tiles_url=self.tiles_url,
- tiles_size=self.tiles_size,
- tiles_minzoom=self.tiles_minzoom,
- tiles_maxzoom=self.tiles_maxzoom,
- tiles_bounds=self.tiles_bounds if self.tiles_bounds else 'undefined'))
+ self.map.add_layer(self.layer)
class LinestringViz(MapViz):
@@ -737,121 +536,36 @@ def __init__(self,
"""
super(LinestringViz, self).__init__(data, *args, **kwargs)
-
- self.vector_url = vector_url
- self.vector_layer_name = vector_layer_name
- self.vector_join_property = vector_join_property
- self.data_join_property = data_join_property
-
- if self.vector_url is not None and self.vector_layer_name is not None:
- self.template = 'vector_linestring'
- self.vector_source = True
- else:
- self.vector_source = False
- self.template = 'linestring'
-
- self.label_property = label_property
- self.label_color = label_color
- self.label_size = label_size
- self.label_halo_color = label_halo_color
- self.label_halo_width = label_halo_width
- self.color_property = color_property
- self.color_stops = color_stops
- self.color_default = color_default
- self.color_function_type = color_function_type
- self.line_stroke = line_stroke
- self.line_width_property = line_width_property
- self.line_width_stops = line_width_stops
- self.line_width_default = line_width_default
- self.line_width_function_type = line_width_function_type
- self.legend_key_shape = legend_key_shape
+
+ self.__dict__['layer'] = LinestringLayer(
+ data=data,
+ vector_url=vector_url,
+ vector_layer_name=vector_layer_name,
+ vector_join_property=vector_join_property,
+ data_join_property=data_join_property,
+ label_property=label_property,
+ label_size=label_size,
+ label_color=label_color,
+ label_halo_color=label_halo_color,
+ label_halo_width=label_halo_width,
+ color_property=color_property,
+ color_stops=color_stops,
+ color_default=color_default,
+ color_function_type=color_function_type,
+ line_stroke=line_stroke,
+ line_width_property=line_width_property,
+ line_width_stops=line_width_stops,
+ line_width_default=line_width_default,
+ line_width_function_type=line_width_function_type,
+ legend_key_shape=legend_key_shape,
+ )
+
+ self.map.add_layer(self.layer)
def generate_vector_color_map(self):
"""Generate color stops array for use with match expression in mapbox template"""
- vector_stops = []
- for row in self.data:
-
- # map color to JSON feature using color_property
- color = color_map(row[self.color_property], self.color_stops, self.color_default)
-
- # link to vector feature using data_join_property (from JSON object)
- vector_stops.append([row[self.data_join_property], color])
-
- return vector_stops
+ return self.layer.generate_vector_color_map()
def generate_vector_width_map(self):
"""Generate width stops array for use with match expression in mapbox template"""
- vector_stops = []
-
- if self.line_width_function_type == 'match':
- match_width = self.line_width_stops
-
- for row in self.data:
-
- # map width to JSON feature using width_property
- width = numeric_map(row[self.line_width_property], self.line_width_stops, self.line_width_default)
-
- # link to vector feature using data_join_property (from JSON object)
- vector_stops.append([row[self.data_join_property], width])
-
- return vector_stops
-
- def add_unique_template_variables(self, options):
- """Update map template variables specific to linestring visual"""
-
- # set line stroke dash interval based on line_stroke property
- if self.line_stroke in ["dashed", "--"]:
- self.line_dash_array = [6, 4]
- elif self.line_stroke in ["dotted", ":"]:
- self.line_dash_array = [0.5, 4]
- elif self.line_stroke in ["dash dot", "-."]:
- self.line_dash_array = [6, 4, 0.5, 4]
- elif self.line_stroke in ["solid", "-"]:
- self.line_dash_array = [1, 0]
- else:
- # default to solid line
- self.line_dash_array = [1, 0]
-
- # common variables for vector and geojson-based linestring maps
- options.update(dict(
- colorStops=self.color_stops,
- colorProperty=self.color_property,
- colorType=self.color_function_type,
- defaultColor=self.color_default,
- lineColor=self.color_default,
- lineDashArray=self.line_dash_array,
- lineStroke=self.line_stroke,
- widthStops=self.line_width_stops,
- widthProperty=self.line_width_property,
- widthType=self.line_width_function_type,
- defaultWidth=self.line_width_default,
- labelColor=self.label_color,
- labelSize=self.label_size,
- labelHaloColor=self.label_halo_color,
- labelHaloWidth=self.label_halo_width
- ))
-
- # vector-based linestring map variables
- if self.vector_source:
- options.update(dict(
- vectorUrl=self.vector_url,
- vectorLayer=self.vector_layer_name,
- vectorJoinDataProperty=self.vector_join_property,
- vectorColorStops=[[0,self.color_default]],
- vectorWidthStops=[[0,self.line_width_default]],
- joinData=json.dumps(self.data, ensure_ascii=False),
- dataJoinProperty=self.data_join_property,
- ))
-
- if self.color_property:
- options.update(dict(vectorColorStops=self.generate_vector_color_map()))
-
- if self.line_width_property:
- options.update(dict(vectorWidthStops=self.generate_vector_width_map()))
-
- # geojson-based linestring map variables
- else:
- options.update(dict(
- geojson_data=json.dumps(self.data, ensure_ascii=False),
- ))
-
+ return self.layer.generate_vector_width_map()