Skip to content

Commit

Permalink
[geo] Added DeckGL GeoJson layer (apache#4097)
Browse files Browse the repository at this point in the history
* added deckgl geojson layer

* linting

* fixed comments

* addressed comments

* added override with controls.color_picker > 0

* set var properly

* set colors if property doesnt exist at all

* refacator on property mapping
  • Loading branch information
hughhhh authored and mistercrunch committed Dec 22, 2017
1 parent ebcf3e9 commit d5e3201
Show file tree
Hide file tree
Showing 9 changed files with 192 additions and 1 deletion.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
37 changes: 36 additions & 1 deletion superset/assets/javascripts/explore/stores/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ const timeColumnOption = {
verbose_name: 'Time',
column_name: '__timestamp',
description: t(
'A reference to the [Time] configuration, taking granularity into ' +
'A reference to the [Time] configuration, taking granularity into ' +
'account'),
};
const sortAxisChoices = [
Expand Down Expand Up @@ -152,6 +152,22 @@ export const controls = {
renderTrigger: true,
},

fill_color_picker: {
label: t('Fill Color'),
description: t(' Set the opacity to 0 if you do not want to override the color specified in the GeoJSON'),
type: 'ColorPickerControl',
default: colorPrimary,
renderTrigger: true,
},

stroke_color_picker: {
label: t('Stroke Color'),
description: t(' Set the opacity to 0 if you do not want to override the color specified in the GeoJSON'),
type: 'ColorPickerControl',
default: colorPrimary,
renderTrigger: true,
},

metric: {
type: 'SelectControl',
label: t('Metric'),
Expand Down Expand Up @@ -505,6 +521,25 @@ export const controls = {
}),
},

geojson: {
type: 'SelectControl',
label: t('GeoJson Column'),
validators: [v.nonEmpty],
description: t('Select the geojson column'),
mapStateToProps: state => ({
choices: (state.datasource) ? state.datasource.all_cols : [],
}),
},

point_radius_scale: {
type: 'SelectControl',
freeForm: true,
label: t('Point Radius Scale'),
validators: [v.integer],
default: null,
choices: formatSelectOptions([0, 100, 200, 300, 500]),
},

all_columns_x: {
type: 'SelectControl',
label: 'X',
Expand Down
27 changes: 27 additions & 0 deletions superset/assets/javascripts/explore/stores/visTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -455,6 +455,33 @@ export const visTypes = {
},
},

deck_geojson: {
label: t('Deck.gl - geoJson'),
requiresTime: true,
controlPanelSections: [
{
label: t('Query'),
expanded: true,
controlSetRows: [
['geojson', 'row_limit'],
],
},
{
label: t('Map'),
controlSetRows: [
['mapbox_style', 'viewport'],
],
},
{
label: t('GeoJson Settings'),
controlSetRows: [
['fill_color_picker', 'stroke_color_picker'],
['point_radius_scale', null],
],
},
],
},

deck_scatter: {
label: t('Deck.gl - Scatter plot'),
requiresTime: true,
Expand Down
67 changes: 67 additions & 0 deletions superset/assets/visualizations/deckgl/geojson.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { GeoJsonLayer } from 'deck.gl';
import { hexToRGB } from '../../javascripts/modules/colors';

import DeckGLContainer from './DeckGLContainer';

const propertyMap = {
fillColor: 'fillColor',
color: 'fillColor',
fill: 'fillColor',
'fill-color': 'fillColor',
strokeColor: 'strokeColor',
'stroke-color': 'strokeColor',
'stroke-width': 'strokeWidth',
};

const convertGeoJsonColorProps = (p, colors) => {
const obj = Object.assign(...Object.keys(p).map(k => ({
[(propertyMap[k]) ? propertyMap[k] : k]: p[k] })));

return {
...obj,
fillColor: (colors.fillColor[3] !== 0) ? colors.fillColor : hexToRGB(obj.fillColor),
strokeColor: (colors.strokeColor[3] !== 0) ? colors.strokeColor : hexToRGB(obj.strokeColor),
};
};

function DeckGeoJsonLayer(slice, payload, setControlValue) {
const fd = slice.formData;
const fc = fd.fill_color_picker;
const sc = fd.stroke_color_picker;
const data = payload.data.geojson.features.map(d => ({
...d,
properties: convertGeoJsonColorProps(
d.properties, {
fillColor: [fc.r, fc.g, fc.b, 255 * fc.a],
strokeColor: [sc.r, sc.g, sc.b, 255 * sc.a],
}),
}));

const layer = new GeoJsonLayer({
id: 'geojson-layer',
data,
filled: true,
stroked: false,
extruded: true,
pointRadiusScale: fd.point_radius_scale,
});

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 = DeckGeoJsonLayer;
2 changes: 2 additions & 0 deletions superset/assets/visualizations/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const VIZ_TYPES = {
deck_grid: 'deck_grid',
deck_hex: 'deck_hex',
deck_path: 'deck_path',
deck_geojson: 'deck_geojson',
};

const vizMap = {
Expand Down Expand Up @@ -88,5 +89,6 @@ const vizMap = {
[VIZ_TYPES.deck_grid]: require('./deckgl/grid.jsx'),
[VIZ_TYPES.deck_hex]: require('./deckgl/hex.jsx'),
[VIZ_TYPES.deck_path]: require('./deckgl/path.jsx'),
[VIZ_TYPES.deck_geojson]: require('./deckgl/geojson.jsx'),
};
export default vizMap;
3 changes: 3 additions & 0 deletions superset/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ def load_examples(load_test_data):
print('Loading DECK.gl demo')
data.load_deck_dash()

print('Loading Paris geojson data')
data.load_paris_iris_geojson()

if load_test_data:
print('Loading [Unicode test data]')
data.load_unicode_test_data()
Expand Down
30 changes: 30 additions & 0 deletions superset/data/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -1522,6 +1522,36 @@ def load_flights():
obj.fetch_metadata()


def load_paris_iris_geojson():
tbl_name = 'paris_iris_mapping'

with gzip.open(os.path.join(DATA_FOLDER, 'paris_iris.json.gz')) as f:
df = pd.read_json(f)
df['features'] = df.features.map(json.dumps)

df.to_sql(
tbl_name,
db.engine,
if_exists='replace',
chunksize=500,
dtype={
'color': String(255),
'name': String(255),
'features': Text,
'type': 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 = "Map of Paris"
tbl.database = get_or_create_main_db()
db.session.merge(tbl)
db.session.commit()
tbl.fetch_metadata()


def load_bart_lines():
tbl_name = 'bart_lines'
with gzip.open(os.path.join(DATA_FOLDER, 'bart-lines.json.gz')) as f:
Expand Down
Binary file added superset/data/paris_iris.json.gz
Binary file not shown.
27 changes: 27 additions & 0 deletions superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -1942,6 +1942,33 @@ class DeckHex(BaseDeckGLViz):
verbose_name = _('Deck.gl - 3D HEX')


class DeckGeoJson(BaseDeckGLViz):

"""deck.gl's GeoJSONLayer"""

viz_type = 'deck_geojson'
verbose_name = _('Deck.gl - GeoJSON')

def query_obj(self):
d = super(DeckGeoJson, self).query_obj()
d['columns'] = [self.form_data.get('geojson')]
d['metrics'] = []
d['groupby'] = []
return d

def get_data(self, df):
fd = self.form_data
geojson = {
'type': 'FeatureCollection',
'features': [json.loads(item) for item in df[fd.get('geojson')]],
}

return {
'geojson': geojson,
'mapboxApiKey': config.get('MAPBOX_API_KEY'),
}


class EventFlowViz(BaseViz):

"""A visualization to explore patterns in event sequences"""
Expand Down

0 comments on commit d5e3201

Please sign in to comment.