Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds on prem support to cartoframes vl #505

Merged
merged 6 commits into from
Nov 15, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion cartoframes/assets/vector.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,12 @@

let credentials = @@CREDENTIALS@@;
carto.setDefaultAuth({
user: credentials['user'],
username: credentials['username'],
apiKey: credentials['api_key'] || 'default_public'
});
carto.setDefaultConfig({
serverURL: credentials['base_url'] || `https://${credentials['user']}.carto.com/`
});
var sources = @@SOURCES@@;

map.fitBounds(@@BOUNDS@@, {animate: false});
Expand Down
16 changes: 13 additions & 3 deletions cartoframes/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -123,11 +123,13 @@ class CartoContext(object):

Example:

Create a :py:class:`CartoContext` object
Create a :py:class:`CartoContext` object for a cloud-based CARTO
account.

.. code::

import cartoframes
# if on prem, format is '{host}/user/{username}'
BASEURL = 'https://{}.carto.com/'.format('your carto username')
APIKEY = 'your carto api key'
cc = cartoframes.CartoContext(BASEURL, APIKEY)
Expand All @@ -139,15 +141,22 @@ class CartoContext(object):
sometimes it is necessary to disable SSL verification depending on your
system configuration. You can do this using a `requests Session
<http://docs.python-requests.org/en/master/user/advanced/#session-objects>`__
object as follows: ::
object as follows:

.. code::

import cartoframes
from requests import Session
session = Session()
session.verify = False

# on prem host (e.g., an IP address)
onprem_host = 'your on prem carto host'

cc = cartoframes.CartoContext(
base_url='https://{}.carto.com/'.format('your carto username'),
base_url='{host}/user/{user}'.format(
host=onprem_host,
user='your carto username'),
api_key='your carto api key',
session=session
)
Expand Down Expand Up @@ -752,6 +761,7 @@ def query(self, query, table_name=None, decode_geom=False):
'top_ten' in the CARTO account.

.. code:: python

topten_df = cc.query(
'''
SELECT * FROM
Expand Down
61 changes: 33 additions & 28 deletions cartoframes/contrib/vector.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
the `CARTO VL styling guide
<https://carto.com/developers/carto-vl/guides/styling-points/>`__

Here is an example using the example CartoContext from the :py:class:`Examples <cartoframes.examples.Examples>` class.
Here is an example using the example CartoContext from the :py:class:`Examples
<cartoframes.examples.Examples>` class.

.. code::

Expand All @@ -31,7 +32,8 @@

from .. import utils

class BaseMaps:

class BaseMaps(object): # pylint: disable=too-few-public-methods
"""Supported CARTO vector basemaps. Read more about the styles in the
`CARTO Basemaps repository <https://github.com/CartoDB/basemap-styles>`__.

Expand All @@ -54,7 +56,8 @@ class BaseMaps:
darkmatter = 'DarkMatter'
voyager = 'Voyager'

class QueryLayer(object):

class QueryLayer(object): # pylint: disable=too-few-public-methods,too-many-instance-attributes
"""CARTO VL layer based on an arbitrary query against user database

Args:
Expand Down Expand Up @@ -117,7 +120,7 @@ class QueryLayer(object):
)
"""
def __init__(self, query, color=None, size=None, time=None,
strokeColor=None, strokeWidth=None, interactivity=None):
strokeColor=None, strokeWidth=None, interactivity=None): # pylint: disable=invalid-name
strconv = lambda x: str(x) if x is not None else None

# data source
Expand All @@ -127,8 +130,8 @@ def __init__(self, query, color=None, size=None, time=None,
self.color = color
self.width = strconv(size)
self.filter = time
self.strokeColor = strokeColor
self.strokeWidth = strconv(strokeWidth)
self.strokeColor = strokeColor # pylint: disable=invalid-name
self.strokeWidth = strconv(strokeWidth) # pylint: disable=invalid-name

# internal attributes
self.orig_query = query
Expand Down Expand Up @@ -158,7 +161,7 @@ def _set_interactivity(self, interactivity):
event_default = 'hover'
if interactivity is None:
return
elif isinstance(interactivity, list) or isinstance(interactivity, tuple):
if isinstance(interactivity, (tuple, list)):
self.interactivity = event_default
interactive_cols = '\n'.join(
'@{0}: ${0}'.format(col) for col in interactivity
Expand All @@ -178,7 +181,7 @@ def _set_interactivity(self, interactivity):

self.styling = '\n'.join([interactive_cols, self.styling])

def _get_html_doc(sources, bounds, creds=None, local_sources=None, basemap=None):
def _get_html_doc(sources, bounds, creds=None, basemap=None):
html_template = os.path.join(
os.path.dirname(__file__),
'..',
Expand All @@ -190,7 +193,11 @@ def _get_html_doc(sources, bounds, creds=None, local_sources=None, basemap=None)
with open(html_template, 'r') as html_file:
srcdoc = html_file.read()

credentials = {} if creds is None else dict(user=creds.username(), api_key=creds.key())
credentials = {
'username': creds.username(),
'api_key': creds.key(),
'base_url': creds.base_url()
}
if isinstance(basemap, dict):
token = basemap.get('token', '')
if not 'style' in basemap:
Expand All @@ -201,16 +208,13 @@ def _get_html_doc(sources, bounds, creds=None, local_sources=None, basemap=None)
warn('A Mapbox style usually needs a token')
basemap = basemap.get('style')

return (
srcdoc\
.replace('@@SOURCES@@', json.dumps(sources))
.replace('@@BASEMAPSTYLE@@', basemap)
.replace('@@MAPBOXTOKEN@@', token)
.replace('@@CREDENTIALS@@', json.dumps(credentials))
.replace('@@BOUNDS@@', bounds)
)
return srcdoc.replace('@@SOURCES@@', json.dumps(sources)) \
.replace('@@BASEMAPSTYLE@@', basemap) \
.replace('@@MAPBOXTOKEN@@', token) \
.replace('@@CREDENTIALS@@', json.dumps(credentials)) \
.replace('@@BOUNDS@@', bounds)

class Layer(QueryLayer):
class Layer(QueryLayer): # pylint: disable=too-few-public-methods
"""Layer from a table name. See :py:class:`vector.QueryLayer
<cartoframes.contrib.vector.QueryLayer>` for docs on the style attributes.

Expand All @@ -234,7 +238,7 @@ class Layer(QueryLayer):
example_context)
"""
def __init__(self, table_name, color=None, size=None, time=None,
strokeColor=None, strokeWidth=None, interactivity=None):
strokeColor=None, strokeWidth=None, interactivity=None): # pylint: disable=invalid-name
self.table_source = table_name

super(Layer, self).__init__(
Expand All @@ -247,7 +251,7 @@ def __init__(self, table_name, color=None, size=None, time=None,
interactivity=interactivity
)

class LocalLayer(QueryLayer):
class LocalLayer(QueryLayer): # pylint: disable=too-few-public-methods
"""Create a layer from a GeoDataFrame

TODO: add support for filepath to a GeoJSON file, JSON/dict, or string
Expand All @@ -271,7 +275,7 @@ class LocalLayer(QueryLayer):
vector.vmap([vector.LocalLayer(gdf), ], context=example_context)
"""
def __init__(self, dataframe, color=None, size=None, time=None,
strokeColor=None, strokeWidth=None, interactivity=None):
strokeColor=None, strokeWidth=None, interactivity=None): # pylint: disable=invalid-name
if HAS_GEOPANDAS and isinstance(dataframe, geopandas.GeoDataFrame):
self.geojson_str = dataframe.to_json()
else:
Expand All @@ -296,7 +300,8 @@ def vmap(layers, context, size=(800, 400), basemap=BaseMaps.voyager):
:py:class:`Layer <cartoframes.contrib.vector.Layer>`,
:py:class:`QueryLayer <cartoframes.contrib.vector.QueryLayer>`, or
:py:class:`LocalLayer <cartoframes.contrib.vector.LocalLayer>`.
context (:py:class:`CartoContext <cartoframes.context.CartoContext>`): A :py:class:`CartoContext <cartoframes.context.CartoContext>` instance
context (:py:class:`CartoContext <cartoframes.context.CartoContext>`):
A :py:class:`CartoContext <cartoframes.context.CartoContext>` instance
basemap (str):
- if a `str`, name of a CARTO vector basemap. One of `positron`,
`voyager`, or `darkmatter` from the :obj:`BaseMaps` class
Expand Down Expand Up @@ -357,13 +362,13 @@ def vmap(layers, context, size=(800, 400), basemap=BaseMaps.voyager):
]

if non_local_layers:
bounds = context._get_bounds(non_local_layers)
bounds = '[[{west}, {south}], [{east}, {north}]]'.format(**bounds)
bounds = context._get_bounds(non_local_layers) # pylint: disable=protected-access
bounds = '[[{west}, {south}], [{east}, {north}]]'.format(**bounds)
else:
bounds = '[[-180, -85.0511], [180, 85.0511]]'

jslayers = []
for idx, layer in enumerate(layers):
for _, layer in enumerate(layers):
is_local = isinstance(layer, LocalLayer)
intera = (
dict(event=layer.interactivity, header=layer.header)
Expand All @@ -377,13 +382,13 @@ def vmap(layers, context, size=(800, 400), basemap=BaseMaps.voyager):
'interactivity': intera
})
html = (
'<iframe srcdoc="{content}" width={width} height={height}>'
'</iframe>'
'<iframe srcdoc="{content}" width={width} height={height}>'
'</iframe>'
).format(
width=size[0],
height=size[1],
content=utils.safe_quotes(
_get_html_doc(jslayers, bounds, context.creds, basemap=basemap)
)
)
)
return HTML(html)