[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:
Maxime Beauchemin
2017-12-19 12:38:03 -08:00
committed by GitHub
parent d4e8d57fc4
commit e4903e6dc6
10 changed files with 182 additions and 9 deletions

View File

@@ -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',

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 KiB

View File

@@ -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;

View File

@@ -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,

View 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;

View File

@@ -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;

View File

@@ -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',

View File

@@ -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()

Binary file not shown.

View File

@@ -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"""