mistercrunch closed pull request #4097: [geo] Added DeckGL GeoJson layer
URL: https://github.com/apache/incubator-superset/pull/4097
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/superset/assets/images/viz_thumbnails/deck_geojson.png
b/superset/assets/images/viz_thumbnails/deck_geojson.png
new file mode 100644
index 0000000000..40d1b63f3b
Binary files /dev/null and
b/superset/assets/images/viz_thumbnails/deck_geojson.png differ
diff --git a/superset/assets/javascripts/explore/stores/controls.jsx
b/superset/assets/javascripts/explore/stores/controls.jsx
index 7117712d21..47ceaa0458 100644
--- a/superset/assets/javascripts/explore/stores/controls.jsx
+++ b/superset/assets/javascripts/explore/stores/controls.jsx
@@ -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 = [
@@ -143,6 +143,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'),
@@ -496,6 +512,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',
diff --git a/superset/assets/javascripts/explore/stores/visTypes.js
b/superset/assets/javascripts/explore/stores/visTypes.js
index 2c5f2a61e1..8e8a7f018e 100644
--- a/superset/assets/javascripts/explore/stores/visTypes.js
+++ b/superset/assets/javascripts/explore/stores/visTypes.js
@@ -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,
diff --git a/superset/assets/visualizations/deckgl/geojson.jsx
b/superset/assets/visualizations/deckgl/geojson.jsx
new file mode 100644
index 0000000000..080d7ee3f1
--- /dev/null
+++ b/superset/assets/visualizations/deckgl/geojson.jsx
@@ -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;
diff --git a/superset/assets/visualizations/main.js
b/superset/assets/visualizations/main.js
index 06d30a1bb1..e692c107d8 100644
--- a/superset/assets/visualizations/main.js
+++ b/superset/assets/visualizations/main.js
@@ -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 = {
@@ -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;
diff --git a/superset/cli.py b/superset/cli.py
index 56ead72b41..14a592bed1 100755
--- a/superset/cli.py
+++ b/superset/cli.py
@@ -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()
diff --git a/superset/data/__init__.py b/superset/data/__init__.py
index b3cb4a8ea9..bddc01432a 100644
--- a/superset/data/__init__.py
+++ b/superset/data/__init__.py
@@ -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:
diff --git a/superset/data/paris_iris.json.gz b/superset/data/paris_iris.json.gz
new file mode 100644
index 0000000000..4a964c94f7
Binary files /dev/null and b/superset/data/paris_iris.json.gz differ
diff --git a/superset/viz.py b/superset/viz.py
index 55f26034db..2a6b4940c9 100644
--- a/superset/viz.py
+++ b/superset/viz.py
@@ -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"""
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
With regards,
Apache Git Services