Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[geo] add support for deck.gl's path layer #4067

Merged
merged 2 commits into from Dec 19, 2017
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions setup.py
Expand Up @@ -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',
Expand Down
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 38 additions & 0 deletions superset/assets/javascripts/explore/stores/controls.jsx
Expand Up @@ -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;
24 changes: 24 additions & 0 deletions superset/assets/javascripts/explore/stores/visTypes.js
Expand Up @@ -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,
Expand Down
39 changes: 39 additions & 0 deletions 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;
2 changes: 2 additions & 0 deletions superset/assets/visualizations/main.js
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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;
3 changes: 3 additions & 0 deletions superset/cli.py
Expand Up @@ -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',
Expand Down
33 changes: 32 additions & 1 deletion superset/data/__init__.py
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Binary file added superset/data/bart-lines.json.gz
Binary file not shown.
51 changes: 43 additions & 8 deletions superset/viz.py
Expand Up @@ -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
Expand Down Expand Up @@ -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')]
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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"""
Expand Down