Skip to content

Commit

Permalink
[geo] add support for deck.gl's path layer
Browse files Browse the repository at this point in the history
Works with json and polyline data.
  • Loading branch information
mistercrunch committed Dec 19, 2017
1 parent ff4f9b4 commit 75f6d45
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 9 deletions.
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

0 comments on commit 75f6d45

Please sign in to comment.