Compare commits

...

6 Commits

Author SHA1 Message Date
Maxime Beauchemin
eea9e68801 [geo] add support for deck.gl's path layer
Works with json and polyline data.

(cherry picked from commit 63e1cf77f99aba5845e593b0a1c4512085d16c64)
2017-12-15 12:21:08 -08:00
Maxime Beauchemin
e62b52f08e Resolving conflicts 2017-12-15 11:57:25 -08:00
Hugh Miles
7ad740461e Merge conflicts 2017-12-11 13:43:37 -08:00
Hugh Miles
1060484836 Merge conflicts 2017-12-11 13:43:04 -08:00
Maxime Beauchemin
445f1e204c Simplify login form for oauth
(cherry picked from commit 89ba06d9a6)
2017-12-11 13:41:25 -08:00
Maxime Beauchemin
010a69de83 0.22.0 2017-12-11 13:39:14 -08:00
12 changed files with 247 additions and 12 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

@@ -1723,5 +1723,37 @@ 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'),
},
};
export default controls;

View File

@@ -397,6 +397,28 @@ export const visTypes = {
},
},
deck_path: {
label: t('Deck.gl - Grid'),
requiresTime: true,
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['line_column', 'line_type'],
],
},
{
label: t('Map'),
expanded: true,
controlSetRows: [
['mapbox_style', 'viewport'],
['color_picker', 'line_width'],
],
},
],
},
deck_screengrid: {
label: t('Deck.gl - Screen grid'),
requiresTime: true,

View File

@@ -0,0 +1,41 @@
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) => {
return {
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

@@ -41,5 +41,6 @@ const vizMap = {
deck_screengrid: require('./deckgl/screengrid.jsx'),
deck_grid: require('./deckgl/grid.jsx'),
deck_hex: require('./deckgl/hex.jsx'),
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

@@ -0,0 +1,15 @@
{% extends "appbuilder/base.html" %}
{% block content %}
<div class="container">
<div id="loginbox" style="margin-top:50px;" class="mainbox col-md-6 col-md-offset-3 col-sm-8 col-sm-offset-2">
<center>
<a href="/login/google">
<img width="300" src="https://developers.google.com/accounts/images/sign-in-with-google.png">
</a>
</center>
</div>
</div>
{% endblock %}

View File

@@ -998,8 +998,7 @@ class Superset(BaseSupersetView):
mimetype='application/json')
@log_this
@has_access_api
@expose('/explore_json/<datasource_type>/<datasource_id>/')
@expose("/explore_json/<datasource_type>/<datasource_id>/")
def explore_json(self, datasource_type, datasource_id):
try:
viz_obj = self.get_viz(
@@ -1012,7 +1011,7 @@ class Superset(BaseSupersetView):
utils.error_msg_from_exception(e),
stacktrace=traceback.format_exc())
if not self.datasource_access(viz_obj.datasource):
if not self.datasource_access(viz_obj.datasource) and request.args.get("restful") != "true":
return json_error_response(DATASOURCE_ACCESS_ERR, status=404)
if request.args.get('csv') == 'true':
@@ -1788,6 +1787,7 @@ class Superset(BaseSupersetView):
@expose('/dashboard/<dashboard_id>/')
def dashboard(self, dashboard_id):
"""Server side rendering for a dashboard"""
logging.info("in dashboard")
session = db.session()
qry = session.query(models.Dashboard)
if dashboard_id.isdigit():
@@ -1845,6 +1845,61 @@ class Superset(BaseSupersetView):
bootstrap_data=json.dumps(bootstrap_data),
)
@expose("/dashboard_json/<dashboard_id>/")
def dashboard_json(self, dashboard_id):
"""Server side rendering for a dashboard"""
session = db.session()
qry = session.query(models.Dashboard)
if dashboard_id.isdigit():
qry = qry.filter_by(id=int(dashboard_id))
else:
qry = qry.filter_by(slug=dashboard_id)
dash = qry.one()
datasources = set()
for slc in dash.slices:
datasource = slc.datasource
if datasource:
datasources.add(datasource)
# Commenting until we figure out Authentication from a service
# for datasource in datasources:
# if datasource and not self.datasource_access(datasource):
# flash(
# __(get_datasource_access_error_msg(datasource.name)),
# "danger")
# return redirect(
# 'superset/request_access/?'
# 'dashboard_id={dash.id}&'.format(**locals()))
# Hack to log the dashboard_id properly, even when getting a slug
@log_this
def dashboard(**kwargs): # noqa
pass
dashboard(dashboard_id=dash.id)
dash_edit_perm = check_ownership(dash, raise_if_false=False)
dash_save_perm = \
dash_edit_perm and self.can_access('can_save_dash', 'Superset')
standalone_mode = request.args.get("standalone") == "true"
dashboard_data = dash.data
dashboard_data.update({
'standalone_mode': standalone_mode,
'dash_save_perm': dash_save_perm,
'dash_edit_perm': dash_edit_perm,
})
bootstrap_data = {
'user_id': g.user.get_id(),
'dashboard_data': dashboard_data,
'datasources': {ds.uid: ds.data for ds in datasources},
'common': self.common_bootsrap_payload(),
}
return json_success(json.dumps(bootstrap_data))
@has_access
@expose('/sync_druid/', methods=['POST'])
@log_this

View File

@@ -30,6 +30,7 @@ from pandas.tseries.frequencies import to_offset
import simplejson as json
from six import PY3, string_types, text_type
from six.moves import reduce
import polyline
from superset import app, cache, get_manifest_file, utils
from superset.utils import DTTM_ALIAS, merge_extra_filters
@@ -1814,13 +1815,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')]
@@ -1881,8 +1883,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):
@@ -1917,6 +1921,36 @@ 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')]]
d = {
'mapboxApiKey': config.get('MAPBOX_API_KEY'),
'paths': paths,
}
return d
class DeckHex(BaseDeckGLViz):
"""deck.gl's DeckLayer"""