From eb12e3455b26e1971523013e63bc5a70e58ed6d8 Mon Sep 17 00:00:00 2001 From: Fletcher Foti Date: Mon, 1 Sep 2014 21:08:23 -0700 Subject: [PATCH 1/4] adding the ability to run dframe_explorer from geopandas basically we monkey patch geopandas to have an explore method that calls dframe_explorer with all the right configuration. This is nice because geopandas * can convert to 4326 * has the center of the shapefile already * can read shapefiles rather than geojson into a leaflet map * can convert to geojson on the fly so that no file on the filesystem is required This also calls the webbrowser automatically when everything is ready --- urbansim/__init__.py | 3 ++ urbansim/geopandaspatch.py | 55 ++++++++++++++++++++++++++++++ urbansim/maps/dframe_explorer.html | 8 +++++ urbansim/maps/dframe_explorer.py | 28 ++++++++++++--- 4 files changed, 90 insertions(+), 4 deletions(-) create mode 100644 urbansim/geopandaspatch.py diff --git a/urbansim/__init__.py b/urbansim/__init__.py index 251a69f9..1e5b5ebe 100644 --- a/urbansim/__init__.py +++ b/urbansim/__init__.py @@ -1,2 +1,5 @@ from .patsypatch import patch_patsy patch_patsy() + +from .geopandaspatch import patch_geopandas +patch_geopandas() diff --git a/urbansim/geopandaspatch.py b/urbansim/geopandaspatch.py new file mode 100644 index 00000000..acbbdf26 --- /dev/null +++ b/urbansim/geopandaspatch.py @@ -0,0 +1,55 @@ +from urbansim.maps import dframe_explorer +import pandas as pd + + +def explore(self, + dataframe_d=None, + center=None, + zoom=11, + geom_name=None, # from JSON file, use index if None + join_name='zone_id', # from data frames + precision=2, + port=8765, + host='localhost', + testing=False): + + if dataframe_d is None: + dataframe_d = {} + + # add the geodataframe + df = pd.DataFrame(self) + if geom_name is None: + df[join_name] = df.index + dataframe_d["local"] = df + + # need to check if it's already 4326 + if self.crs != 4326: + self = self.to_crs(epsg=4326) + + bbox = self.total_bounds + if center is None: + center = [(bbox[1]+bbox[3])/2, (bbox[0]+bbox[2])/2] + + self.to_json() + + dframe_explorer.start( + dataframe_d, + center=center, + zoom=zoom, + shape_json=self.to_json(), + geom_name=geom_name, # from JSON file + join_name=join_name, # from data frames + precision=precision, + port=port, + host=host, + testing=testing + ) + + +def patch_geopandas(): + """ + Add a new function to the geodataframe called explore which uses the + urbansim function dataframe_explorer. + """ + import geopandas + geopandas.GeoDataFrame.explore = explore diff --git a/urbansim/maps/dframe_explorer.html b/urbansim/maps/dframe_explorer.html index b1f2bd60..2625f9f3 100644 --- a/urbansim/maps/dframe_explorer.html +++ b/urbansim/maps/dframe_explorer.html @@ -200,7 +200,11 @@ function style_f(feature) { if(data && q) { + {% if geom_name %} var val = data[feature.properties[GEOMNAME]]; + {% else %} + var val = data[feature.id]; + {% endif %} var v = q(val); } fo = .7; @@ -225,7 +229,11 @@ shapeLayer = new L.geoJson(zones, { style: style_f, onEachFeature: function (feature, layer) { + {% if geom_name %} feature_d[feature.properties[GEOMNAME]] = layer; + {% else %} + feature_d[feature.id] = layer; + {% endif %} //layer.bindPopup("No value"); } }); diff --git a/urbansim/maps/dframe_explorer.py b/urbansim/maps/dframe_explorer.py index a8fd0be8..38296131 100644 --- a/urbansim/maps/dframe_explorer.py +++ b/urbansim/maps/dframe_explorer.py @@ -4,6 +4,8 @@ import numpy as np import pandas as pd import os +import json +import webbrowser from jinja2 import Environment @@ -60,6 +62,8 @@ def index(): @route('/data/') def data_static(filename): + if filename == "internal": + return SHAPES return static_file(filename, root='./data') @@ -89,10 +93,12 @@ def start(views, zoom : int The initial zoom level of the map shape_json : str - The path to the geojson file which contains that shapes that will be - displayed + Can either be the geojson itself or the path to a file which contains + the geojson that describes the shapes to display (uses os.path.exists + to check for a file on the filesystem) geom_name : str - The field name from the JSON file which contains the id of the geometry + The field name from the JSON file which contains the id of the + geometry - if it's None, use the id of the geojson feature join_name : str The column name from the dataframes passed as views (must be in each view) which joins to geom_name in the shapes @@ -111,9 +117,20 @@ def start(views, queries from a web browser """ - global DFRAMES, CONFIG + global DFRAMES, CONFIG, SHAPES DFRAMES = {str(k): views[k] for k in views} + print shape_json + if not testing and not os.path.exists(shape_json): + # if the file doesn't exist, we try to use it as json + try: + json.loads(shape_json) + except: + assert 0, "The json passed in appears to be neither a parsable " \ + "json format nor a file that exists on the file system" + SHAPES = shape_json + shape_json = "data/internal" + config = { 'center': str(center), 'zoom': zoom, @@ -135,4 +152,7 @@ def start(views, if testing: return + # open in a new tab, if possible + webbrowser.open("http://%s:%s" % (host, port), new=2) + run(host=host, port=port, debug=True) From ba84ff6c16b7a201254fc54e4bb01f56f6ca76a3 Mon Sep 17 00:00:00 2001 From: Fletcher Foti Date: Mon, 1 Sep 2014 21:14:53 -0700 Subject: [PATCH 2/4] geopandas is a dependency of this branch --- setup.py | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.py b/setup.py index c7dbba6c..fa3372de 100644 --- a/setup.py +++ b/setup.py @@ -22,6 +22,7 @@ packages=find_packages(exclude=['*.tests']), install_requires=[ 'bottle>=0.12.5', + 'geopandas>=.1', 'matplotlib>=1.3.1', 'numpy>=1.8.0', 'pandas>=0.13.1', From 38f2586b882dc52f95d94608583bfc752e950217 Mon Sep 17 00:00:00 2001 From: Fletcher Foti Date: Mon, 1 Sep 2014 21:17:32 -0700 Subject: [PATCH 3/4] named the version of geopandas incorrectly --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index fa3372de..042c1a94 100644 --- a/setup.py +++ b/setup.py @@ -22,7 +22,7 @@ packages=find_packages(exclude=['*.tests']), install_requires=[ 'bottle>=0.12.5', - 'geopandas>=.1', + 'geopandas>=0.1.0', 'matplotlib>=1.3.1', 'numpy>=1.8.0', 'pandas>=0.13.1', From 44d350e22c882d6054ddb64357c515da98659fcd Mon Sep 17 00:00:00 2001 From: Fletcher Foti Date: Mon, 1 Sep 2014 21:23:27 -0700 Subject: [PATCH 4/4] appears to have choked on fiona so conda installing --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 06d22229..37dbbb53 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,7 @@ install: - sudo pip install conda - sudo conda init - | - conda create -p $HOME/py --yes ipython-notebook jinja2 matplotlib numpy pandas patsy pip scipy statsmodels pytables pytest pyyaml toolz "python=$TRAVIS_PYTHON_VERSION" + conda create -p $HOME/py --yes ipython-notebook jinja2 matplotlib numpy pandas patsy pip scipy statsmodels pytables pytest pyyaml toolz shapely fiona "python=$TRAVIS_PYTHON_VERSION" - export PATH=$HOME/py/bin:$PATH - pip install simplejson bottle - pip install pytest-cov coveralls pep8