mistercrunch closed pull request #4067: [geo] add support for deck.gl's path
layer
URL: https://github.com/apache/incubator-superset/pull/4067
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/setup.py b/setup.py
index 8d0dba3a0f..0ecd1ef1be 100644
--- a/setup.py
+++ b/setup.py
@@ -66,6 +66,7 @@ def get_git_sha():
'pandas==0.20.3',
'parsedatetime==2.0.0',
'pathlib2==2.3.0',
+ 'polyline==1.3.2',
'pydruid==0.3.1',
'PyHive>=0.4.0',
'python-dateutil==2.6.0',
diff --git a/superset/assets/images/viz_thumbnails/deck_path.png
b/superset/assets/images/viz_thumbnails/deck_path.png
new file mode 100644
index 0000000000..eede9da44c
Binary files /dev/null and
b/superset/assets/images/viz_thumbnails/deck_path.png differ
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx
b/superset/assets/javascripts/explore/stores/controls.jsx
index 006a1b49e3..ee6d63af33 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -1713,5 +1713,43 @@ export const controls = {
t('Partitions whose height to parent height proportions are ' +
'below this value are pruned'),
},
+
+ line_column: {
+ type: 'SelectControl',
+ label: t('Lines column'),
+ default: null,
+ description: t('The database columns that contains lines information'),
+ mapStateToProps: state => ({
+ choices: (state.datasource) ? state.datasource.all_cols : [],
+ }),
+ validators: [v.nonEmpty],
+ },
+ line_type: {
+ type: 'SelectControl',
+ label: t('Lines encoding'),
+ clearable: false,
+ default: 'json',
+ description: t('The encoding format of the lines'),
+ choices: [
+ ['polyline', 'Polyline'],
+ ['json', 'JSON'],
+ ],
+ },
+
+ line_width: {
+ type: 'TextControl',
+ label: t('Line width'),
+ renderTrigger: true,
+ isInt: true,
+ default: 10,
+ description: t('The width of the lines'),
+ },
+
+ reverse_long_lat: {
+ type: 'CheckboxControl',
+ label: t('Reverse Lat & Long'),
+ default: false,
+ },
+
};
export default controls;
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js
b/superset/assets/javascripts/explore/stores/visTypes.js
index ef9dc4112c..2c5f2a61e1 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -397,6 +397,30 @@ export const visTypes = {
},
},
+ deck_path: {
+ label: t('Deck.gl - Grid'),
+ requiresTime: true,
+ controlPanelSections: [
+ {
+ label: t('Query'),
+ expanded: true,
+ controlSetRows: [
+ ['line_column', 'line_type'],
+ ['row_limit', null],
+ ],
+ },
+ {
+ label: t('Map'),
+ expanded: true,
+ controlSetRows: [
+ ['mapbox_style', 'viewport'],
+ ['color_picker', 'line_width'],
+ ['reverse_long_lat', null],
+ ],
+ },
+ ],
+ },
+
deck_screengrid: {
label: t('Deck.gl - Screen grid'),
requiresTime: true,
diff --git a/superset/assets/visualizations/deckgl/path.jsx
b/superset/assets/visualizations/deckgl/path.jsx
new file mode 100644
index 0000000000..c814adc501
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/path.jsx
@@ -0,0 +1,39 @@
+import React from 'react';
+import ReactDOM from 'react-dom';
+import { PathLayer } from 'deck.gl';
+
+import DeckGLContainer from './DeckGLContainer';
+
+function deckPath(slice, payload, setControlValue) {
+ const fd = slice.formData;
+ const c = fd.color_picker;
+ const fixedColor = [c.r, c.g, c.b, 255 * c.a];
+ const data = payload.data.paths.map(path => ({
+ path,
+ width: fd.line_width,
+ color: fixedColor,
+ }));
+
+ const layer = new PathLayer({
+ id: `path-layer-${slice.containerId}`,
+ data,
+ rounded: true,
+ widthScale: 1,
+ });
+ const viewport = {
+ ...fd.viewport,
+ width: slice.width(),
+ height: slice.height(),
+ };
+ ReactDOM.render(
+ <DeckGLContainer
+ mapboxApiAccessToken={payload.data.mapboxApiKey}
+ viewport={viewport}
+ layers={[layer]}
+ mapStyle={fd.mapbox_style}
+ setControlValue={setControlValue}
+ />,
+ document.getElementById(slice.containerId),
+ );
+}
+module.exports = deckPath;
diff --git a/superset/assets/visualizations/main.js
b/superset/assets/visualizations/main.js
index fdcb7b1eab..06d30a1bb1 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -42,6 +42,7 @@ export const VIZ_TYPES = {
deck_screengrid: 'deck_screengrid',
deck_grid: 'deck_grid',
deck_hex: 'deck_hex',
+ deck_path: 'deck_path',
};
const vizMap = {
@@ -86,5 +87,6 @@ const vizMap = {
[VIZ_TYPES.deck_screengrid]: require('./deckgl/screengrid.jsx'),
[VIZ_TYPES.deck_grid]: require('./deckgl/grid.jsx'),
[VIZ_TYPES.deck_hex]: require('./deckgl/hex.jsx'),
+ [VIZ_TYPES.deck_path]: require('./deckgl/path.jsx'),
};
export default vizMap;
diff --git a/superset/cli.py b/superset/cli.py
index 16500ac031..56ead72b41 100755
--- a/superset/cli.py
+++ b/superset/cli.py
@@ -146,6 +146,9 @@ def load_examples(load_test_data):
print('Loading flights data')
data.load_flights()
+ print('Loading bart lines data')
+ data.load_bart_lines()
+
@manager.option(
'-d', '--datasource',
diff --git a/superset/data/__init__.py b/superset/data/__init__.py
index 742a32b4c5..b3cb4a8ea9 100644
--- a/superset/data/__init__.py
+++ b/superset/data/__init__.py
@@ -12,8 +12,9 @@
import textwrap
import pandas as pd
-from sqlalchemy import BigInteger, Date, DateTime, Float, String
+from sqlalchemy import BigInteger, Date, DateTime, Float, String, Text
import geohash
+import polyline
from superset import app, db, utils
from superset.connectors.connector_registry import ConnectorRegistry
@@ -1519,3 +1520,33 @@ def load_flights():
db.session.merge(obj)
db.session.commit()
obj.fetch_metadata()
+
+
+def load_bart_lines():
+ tbl_name = 'bart_lines'
+ with gzip.open(os.path.join(DATA_FOLDER, 'bart-lines.json.gz')) as f:
+ df = pd.read_json(f, encoding='latin-1')
+ df['path_json'] = df.path.map(json.dumps)
+ df['polyline'] = df.path.map(polyline.encode)
+ del df['path']
+ df.to_sql(
+ tbl_name,
+ db.engine,
+ if_exists='replace',
+ chunksize=500,
+ dtype={
+ 'color': String(255),
+ 'name': String(255),
+ 'polyline': Text,
+ 'path_json': Text,
+ },
+ index=False)
+ print("Creating table {} reference".format(tbl_name))
+ tbl = db.session.query(TBL).filter_by(table_name=tbl_name).first()
+ if not tbl:
+ tbl = TBL(table_name=tbl_name)
+ tbl.description = "BART lines"
+ tbl.database = get_or_create_main_db()
+ db.session.merge(tbl)
+ db.session.commit()
+ tbl.fetch_metadata()
diff --git a/superset/data/bart-lines.json.gz b/superset/data/bart-lines.json.gz
new file mode 100644
index 0000000000..91f50fbe6a
Binary files /dev/null and b/superset/data/bart-lines.json.gz differ
diff --git a/superset/viz.py b/superset/viz.py
index 85bc854cf9..55f26034db 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -27,6 +27,7 @@
import numpy as np
import pandas as pd
from pandas.tseries.frequencies import to_offset
+import polyline
import simplejson as json
from six import PY3, string_types, text_type
from six.moves import reduce
@@ -1796,13 +1797,14 @@ def query_obj(self):
gb = []
spatial = fd.get('spatial')
- if spatial.get('type') == 'latlong':
- gb += [spatial.get('lonCol')]
- gb += [spatial.get('latCol')]
- elif spatial.get('type') == 'delimited':
- gb += [spatial.get('lonlatCol')]
- elif spatial.get('type') == 'geohash':
- gb += [spatial.get('geohashCol')]
+ if spatial:
+ if spatial.get('type') == 'latlong':
+ gb += [spatial.get('lonCol')]
+ gb += [spatial.get('latCol')]
+ elif spatial.get('type') == 'delimited':
+ gb += [spatial.get('lonlatCol')]
+ elif spatial.get('type') == 'geohash':
+ gb += [spatial.get('geohashCol')]
if fd.get('dimension'):
gb += [fd.get('dimension')]
@@ -1863,8 +1865,10 @@ def query_obj(self):
return super(DeckScatterViz, self).query_obj()
def get_metrics(self):
+ self.metric = None
if self.point_radius_fixed.get('type') == 'metric':
- return [self.point_radius_fixed.get('value')]
+ self.metric = self.point_radius_fixed.get('value')
+ return [self.metric]
return None
def get_properties(self, d):
@@ -1899,6 +1903,37 @@ class DeckGrid(BaseDeckGLViz):
verbose_name = _('Deck.gl - 3D Grid')
+class DeckPathViz(BaseDeckGLViz):
+
+ """deck.gl's PathLayer"""
+
+ viz_type = 'deck_path'
+ verbose_name = _('Deck.gl - Paths')
+ deser_map = {
+ 'json': json.loads,
+ 'polyline': polyline.decode,
+ }
+
+ def query_obj(self):
+ d = super(DeckPathViz, self).query_obj()
+ d['groupby'] = []
+ d['metrics'] = []
+ d['columns'] = [self.form_data.get('line_column')]
+ return d
+
+ def get_data(self, df):
+ fd = self.form_data
+ deser = self.deser_map[fd.get('line_type')]
+ paths = [deser(s) for s in df[fd.get('line_column')]]
+ if fd.get('reverse_long_lat'):
+ paths = [[(point[1], point[0]) for point in path] for path in
paths]
+ d = {
+ 'mapboxApiKey': config.get('MAPBOX_API_KEY'),
+ 'paths': paths,
+ }
+ return d
+
+
class DeckHex(BaseDeckGLViz):
"""deck.gl's DeckLayer"""
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services