Skip to content

Commit

Permalink
ZIP codes based on Polygon
Browse files Browse the repository at this point in the history
  • Loading branch information
betodealmeida committed Oct 25, 2018
1 parent bfbd83b commit 0dbaaf9
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 3 deletions.
25 changes: 25 additions & 0 deletions superset/assets/src/explore/components/controls/SpatialControl.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const spatialTypes = {
latlong: 'latlong',
delimited: 'delimited',
geohash: 'geohash',
zipcode: 'zipcode',
};

const propTypes = {
Expand Down Expand Up @@ -45,6 +46,7 @@ export default class SpatialControl extends React.Component {
lonlatCol: v.lonlatCol || defaultCol,
reverseCheckbox: v.reverseCheckbox || false,
geohashCol: v.geohashCol || defaultCol,
zipcodeCol: v.zipcodeCol || defaultCol,
value: null,
errors: [],
};
Expand Down Expand Up @@ -79,6 +81,12 @@ export default class SpatialControl extends React.Component {
if (!value.geohashCol) {
errors.push(errMsg);
}
} else if (type === spatialTypes.zipcode) {
value.zipcodeCol = this.state.zipcodeCol;
value.reverseCheckbox = this.state.reverseCheckbox;
if (!value.zipcodeCol) {
errors.push(errMsg);
}
}
this.setState({ value, errors });
this.props.onChange(value, errors);
Expand All @@ -102,6 +110,8 @@ export default class SpatialControl extends React.Component {
return `${this.state.lonlatCol}`;
} else if (this.state.type === spatialTypes.geohash) {
return `${this.state.geohashCol}`;
} else if (this.state.type === spatialTypes.zipcode) {
return `${this.state.zipcodeCol}`;
}
return null;
}
Expand Down Expand Up @@ -181,6 +191,21 @@ export default class SpatialControl extends React.Component {
</Col>
</Row>
</PopoverSection>
<PopoverSection
title={t('ZIP code')}
isSelected={this.state.type === spatialTypes.zipcode}
onSelect={this.setType.bind(this, spatialTypes.zipcode)}
>
<Row>
<Col md={6}>
Column
{this.renderSelect('zipcodeCol', spatialTypes.zipcode)}
</Col>
<Col md={6}>
{this.renderReverseCheckbox()}
</Col>
</Row>
</PopoverSection>
<div className="clearfix">
<Button
bsSize="small"
Expand Down
1 change: 1 addition & 0 deletions superset/assets/src/explore/controls.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2126,6 +2126,7 @@ export const controls = {
['polyline', 'Polyline'],
['json', 'JSON'],
['geohash', 'geohash (square)'],
['zipcode', 'ZIP code'],
],
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ export function getLayer(formData, payload, setTooltip, selected, onSelect, filt
return new PolygonLayer({
id: `path-layer-${fd.slice_id}`,
data,
pickable: true,
filled: fd.filled,
stroked: fd.stroked,
getPolygon: d => d.polygon,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ export function commonLayerProps(formData, setTooltip, onSelect) {
let tooltipContentGenerator;
if (fd.js_tooltip) {
tooltipContentGenerator = sandboxedEval(fd.js_tooltip);
} else if (fd.line_column && fd.line_type === 'geohash') {
} else if (fd.line_column && (fd.line_type === 'geohash' || fd.line_type === 'zipcode')) {
tooltipContentGenerator = o => (
<div>
<div>{fd.line_column}: <strong>{o.object[fd.line_column]}</strong></div>
Expand Down
71 changes: 70 additions & 1 deletion superset/viz.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from itertools import product
import logging
import math
import os
import pickle as pkl
import re
import traceback
Expand All @@ -30,6 +31,7 @@
from past.builtins import basestring
import polyline
import simplejson as json
import sqlalchemy

from superset import app, cache, get_css_manifest_files
from superset.exceptions import NullValueException, SpatialException
Expand Down Expand Up @@ -2083,6 +2085,8 @@ def get_spatial_columns(self, key):
return [spatial.get('lonlatCol')]
elif spatial.get('type') == 'geohash':
return [spatial.get('geohashCol')]
elif spatial.get('type') == 'zipcode':
return [spatial.get('zipcodeCol')]

@staticmethod
def parse_coordinates(s):
Expand All @@ -2100,6 +2104,11 @@ def reverse_geohash_decode(geohash_code):
lat, lng = geohash.decode(geohash_code)
return (lng, lat)

@staticmethod
def reverse_zipcode_decode(zipcode):
raise NotImplementedError(
'No mapping from ZIP code to single latitude/longitude')

@staticmethod
def reverse_latlong(df, key):
df[key] = [
Expand All @@ -2125,6 +2134,9 @@ def process_spatial_data_obj(self, key, df):
elif spatial.get('type') == 'geohash':
df[key] = df[spatial.get('geohashCol')].map(self.reverse_geohash_decode)
del df[spatial.get('geohashCol')]
elif spatial.get('type') == 'zipcode':
df[key] = df[spatial.get('zipcodeCol')].map(self.reverse_zipcode_decode)
del df[spatial.get('zipcodeCol')]

if spatial.get('reverseCheckbox'):
self.reverse_latlong(df, key)
Expand Down Expand Up @@ -2315,6 +2327,56 @@ def geohash_to_json(geohash_code):
]


def zipcode_deser(zipcodes):
geojson = zipcodes_to_json(zipcodes)
def deser(zipcode):
return geojson[str(zipcode)]['coordinates'][0]
return deser


def zipcodes_to_json(zipcodes):
user = os.environ.get('CREDENTIALS_LYFTPG_USER', '')
password = os.environ.get('CREDENTIALS_LYFTPG_PASSWORD', '')
url = (
'postgresql+psycopg2://'
'{user}:{password}'
'@analytics-platform-vpc.c067nfzisc99.us-east-1.rds.amazonaws.com:5432'
'/platform'.format(user=user, password=password)
)

out = {}
missing = set()
for zipcode in zipcodes:
cache_key = 'zipcode_geojson_{}'.format(zipcode)
geojson = cache and cache.get(cache_key)
if geojson:
out[zipcode] = geojson
else:
missing.add(str(zipcode))

if not missing:
return out

# fetch missing geojson from lyftpg
in_clause = ', '.join(['%s'] * len(missing))
query = (
'SELECT zipcode, geojson FROM zip_codes WHERE zipcode IN ({0})'
.format(in_clause))
conn = sqlalchemy.create_engine(url, client_encoding='utf8')
results = conn.execute(query, tuple(missing)).fetchall()

for zipcode, geojson in results:
out[zipcode] = geojson
if cache and len(results) < 10000: # avoid storing too much
cache_key = 'zipcode_geojson_{}'.format(zipcode)
try:
cache.set(cache_key, geojson, timeout=86400)
except Exception:
pass

return out


class DeckPathViz(BaseDeckGLViz):

"""deck.gl's PathLayer"""
Expand All @@ -2327,6 +2389,7 @@ class DeckPathViz(BaseDeckGLViz):
'json': json.loads,
'polyline': polyline.decode,
'geohash': geohash_to_json,
'zipcode': None, # per request
}

def query_obj(self):
Expand All @@ -2352,12 +2415,18 @@ def get_properties(self, d):
if fd.get('reverse_long_lat'):
path = [(o[1], o[0]) for o in path]
d[self.deck_viz_key] = path
if line_type != 'geohash':
if line_type not in ['geohash', 'zipcode']:
del d[line_column]
d['__timestamp'] = d.get(DTTM_ALIAS) or d.get('__time')
return d

def get_data(self, df):
fd = self.form_data
line_type = fd.get('line_type')
if line_type == 'zipcode':
zipcodes = df[fd['line_column']].unique()
self.deser_map['zipcode'] = zipcode_deser(zipcodes)

self.metric_label = self.get_metric_label(self.metric)
return super(DeckPathViz, self).get_data(df)

Expand Down

0 comments on commit 0dbaaf9

Please sign in to comment.