mirror of
https://github.com/apache/superset.git
synced 2026-04-19 08:04:53 +00:00
[geo] add support for deck.gl's path layer (#4067)
* [geo] add support for deck.gl's path layer Works with json and polyline data. * Lint
This commit is contained in:
committed by
GitHub
parent
d4e8d57fc4
commit
e4903e6dc6
1
setup.py
1
setup.py
@@ -66,6 +66,7 @@ setup(
|
||||
'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',
|
||||
|
||||
BIN
superset/assets/images/viz_thumbnails/deck_path.png
Normal file
BIN
superset/assets/images/viz_thumbnails/deck_path.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 511 KiB |
@@ -1721,5 +1721,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;
|
||||
|
||||
@@ -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,
|
||||
|
||||
39
superset/assets/visualizations/deckgl/path.jsx
Normal file
39
superset/assets/visualizations/deckgl/path.jsx
Normal file
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -12,8 +12,9 @@ import random
|
||||
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()
|
||||
|
||||
BIN
superset/data/bart-lines.json.gz
Normal file
BIN
superset/data/bart-lines.json.gz
Normal file
Binary file not shown.
@@ -27,6 +27,7 @@ from markdown import markdown
|
||||
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 @@ class BaseDeckGLViz(BaseViz):
|
||||
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 @@ class DeckScatterViz(BaseDeckGLViz):
|
||||
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"""
|
||||
|
||||
Reference in New Issue
Block a user