diff --git a/.travis.yml b/.travis.yml index 5152c01..7f77eae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,45 +1,39 @@ language: python -sudo: false + python: -- '2.7' -- '3.6' + - '2.7' + - '3.5' + - '3.6' + - '3.7' + - '3.8' -matrix: - allow_failures: - - python: '3.6' # travis hangs in demo notebook - env: PANDANA=">=0.4" - fast_finish: true +services: + - xvfb # allows matplotlib display install: -- wget http://bit.ly/miniconda -O miniconda.sh -- bash miniconda.sh -b -p $HOME/miniconda -- export PATH="$HOME/miniconda/bin:$PATH" -- hash -r -- conda config --set always_yes yes --set changeps1 no -- conda update -q conda -- conda info -a -- conda config --add channels udst -- conda config --add channels conda-forge -- conda create -q -n test-env python=$TRAVIS_PYTHON_VERSION pip numpy pandas pytest jupyter pycodestyle matplotlib scikit-learn pyyaml basemap basemap-data-hires pandana$PANDANA geopy osmnet -- source activate test-env -- conda list -- pip install . - -after_success: -- bin/build_docs.sh + - wget https://repo.continuum.io/miniconda/Miniconda3-latest-Linux-x86_64.sh -O miniconda.sh + - bash miniconda.sh -b -p $HOME/miniconda + - export PATH="$HOME/miniconda/bin:$PATH" + - hash -r + - conda config --set always_yes yes --set show_channel_urls true + - conda update -n base conda + - conda config --prepend channels conda-forge + - conda create -n test-environment python=$TRAVIS_PYTHON_VERSION pyyaml --file requirements-dev.txt + - source activate test-environment + - conda info --all + - pip install . + - pip list + - pip show urbanaccess script: -- pycodestyle urbanaccess -- py.test -- cd demo; jupyter nbconvert --to python simple_example.ipynb -- cd ../urbanaccess/tests/integration; python remove_nb_magic.py -in simple_example.py -out simple_example_clean.py -- cd ../../../demo; python simple_example_clean.py -- cd ../urbanaccess/tests/integration; python integration_madison.py -- python integration_sandiego.py && cd $TRAVIS_BUILD_DIR - -env: - matrix: - - PANDANA="=0.3" - - PANDANA=">=0.4" - global: - secure: SW6dfeQen1oZUYa2pr/qMdJGDK5c3tIbJsF1POLSMtgpbYaRQvogCkCd7sFXiTkxTCaNnz/eQyT2nDyUZJyDmYpntZmRthuN1zDgNk8ziDpmy/PF1bD4eshDr3MeoI4HgNYryl5qD8brEP0up0P/rvaAMoi26w4eyk0r+sO3PDVF6Jy4MwTufY4a4B4qYpi7V8yhExExytshHOEjD8C2IqEQDkwyR9oto2Gx8EpoCJEvxETG+aqLw1xj2UnXnbWND10Ni6pkeRpjAHFv4qM0i35griG2RKu9075Dubz/6UPMvpIEy581Zx5cqaSuOSUzde1L172vLTgOH31lnFXe1flHau1wI2gxOiamSdlVTAnVtL5P1aEtm1L5FjBcPlIs9rHmNGnydByX1Qe16HRLUopgAVm+jLZqWxrlesC5ax3uJ6Q3g0ZyKfmTJ5uWDeRusMVZswBT3NJc0BhkHDq7tE+3fokBfFApJcQwoXIplvRwXPkGFtKnL2IgCMTrKbDzGEYfdm/v7eV2xYe5hrEUIC9cUhkC1Ns2azAERXSgWcyM7ciFH3r1Jz9ixT+fuw9bTXibqcMjAmxdcQAW9y/rIPRJ3GMSdT7WEzYX19naGr+oyKqVMgsHFQ+wL7U5iblAGIy8VLNpE7Aa67dyG6SVAPTGzp0RouYLY9LYiRIybrI= \ No newline at end of file + - pycodestyle urbanaccess + - py.test + - cd demo + - jupyter nbconvert --to python simple_example.ipynb + - cd ../urbanaccess/tests/integration + - python remove_nb_magic.py -in simple_example.py -out simple_example_clean.py + - cd ../../../demo + - python simple_example_clean.py + - cd ../urbanaccess/tests/integration + - python integration_madison.py + - python integration_sandiego.py diff --git a/HISTORY.rst b/CHANGELOG.rst similarity index 84% rename from HISTORY.rst rename to CHANGELOG.rst index 245419e..0c2f8f3 100644 --- a/HISTORY.rst +++ b/CHANGELOG.rst @@ -1,3 +1,11 @@ +v0.2.1 +====== + +2020/08/28 + +* Support for GeoPy 2.0+ +* Support for Pandas 1.0+ + v0.2.0 ====== diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b91e6e2 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,40 @@ +## Preparing a release: + +- Make a new branch for release prep + +- Update the version number and changelog + - `CHANGELOG.md` + - `setup.py` + - `urbanaccess/__init__.py` + - `docs/source/conf.py` + - `docs/source/index.rst` + +- Make sure all the tests are passing, and check if updates are needed to `README.md` or to the documentation + +- Open a pull request to the master branch to finalize it + +- After merging, tag the release on GitHub and follow the distribution procedures below + + +## Distributing a release on PyPI (for pip installation): + +- Register an account at https://pypi.org, ask one of the current maintainers to add you to the project, and `pip install twine` + +- Check out the copy of the code you'd like to release + +- Run `python setup.py sdist bdist_wheel --universal` + +- This should create a `dist` directory containing two package files -- delete any old ones before the next step + +- Run `twine upload dist/*` -- this will prompt you for your pypi.org credentials + +- Check https://pypi.org/project/osmnet/ for the new version + + +## Distributing a release on Conda Forge (for conda installation): + +- The [conda-forge/urbanaccess-feedstock](https://github.com/conda-forge/urbanaccess-feedstock) repository controls the Conda Forge release + +- Conda Forge bots usually detect new releases on PyPI and set in motion the appropriate feedstock updates, which a current maintainer will need to approve and merge + +- Check https://anaconda.org/conda-forge/urbanaccess for the new version (may take a few minutes for it to appear) \ No newline at end of file diff --git a/License.txt b/License.txt index 15432cd..60b56a1 100644 --- a/License.txt +++ b/License.txt @@ -1,8 +1,8 @@ GNU AFFERO GENERAL PUBLIC LICENSE Version 3, 19 November 2007 - Copyright (c) 2016 UrbanSim Inc. - Copyright (c) 2015-2016 Samuel D. Blanchard + Copyright (c) 2020 UrbanSim Inc. + Copyright (c) 2020 Samuel D. Blanchard Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..84fd8bd --- /dev/null +++ b/MANIFEST.in @@ -0,0 +1,4 @@ +# files to include in the source distribution on pypi (setup and README are included automatically) + +include CHANGELOG.rst +include License.txt diff --git a/README.rst b/README.rst index 660eaac..03af455 100644 --- a/README.rst +++ b/README.rst @@ -55,13 +55,6 @@ To cite this tool and for a complete description of the UrbanAccess methodology For other related literature see `here `__. -Current status --------------- - -*Forthcoming improvements:* - -- Unit tests - Reporting bugs -------------- @@ -82,15 +75,15 @@ Install the latest release conda ~~~~~~ -UrbanAccess is available on conda and can be installed with:: +UrbanAccess is available on Conda Forge and can be installed with:: - conda install -c udst urbanaccess + conda install urbanaccess -c conda-forge pip ~~~~~~ UrbanAccess is available on PyPI and can be installed with:: - pip install -U urbanaccess + pip install urbanaccess Development Installation ------------------------ diff --git a/bin/build_docs.sh b/bin/build_docs.sh deleted file mode 100755 index feb5901..0000000 --- a/bin/build_docs.sh +++ /dev/null @@ -1,61 +0,0 @@ -#! /usr/bin/env bash - -# Copied from github.com/sympy/sympy -# -# This file automatically deploys changes to http://udst.github.io/urbanaccess/. -# This will only happen when building a non-pull request build on the master -# branch of Pandana. -# It requires an access token which should be present in .travis.yml file. -# -# Following is the procedure to get the access token: -# -# $ curl -X POST -u -H "Content-Type: application/json" -d\ -# "{\"scopes\":[\"public_repo\"],\"note\":\"token for pushing from travis\"}"\ -# https://api.github.com/authorizations -# -# It'll give you a JSON response having a key called "token". -# -# $ gem install travis -# $ travis encrypt -r sympy/sympy GH_TOKEN= env.global -# -# This will give you an access token("secure"). This helps in creating an -# environment variable named GH_TOKEN while building. -# -# Add this secure code to .travis.yml as described here http://docs.travis-ci.com/user/encryption-keys/ - -# Exit on error -set -e - -ACTUAL_TRAVIS_JOB_NUMBER=`echo $TRAVIS_JOB_NUMBER| cut -d'.' -f 2` - -if [ "$TRAVIS_REPO_SLUG" == "UDST/urbanaccess" ] && \ - [ "$TRAVIS_BRANCH" == "master" ] && \ - [ "$TRAVIS_PULL_REQUEST" == "false" ] && \ - [ "$ACTUAL_TRAVIS_JOB_NUMBER" == "1" ]; then - - echo "Installing dependencies" - conda install --yes --quiet sphinx numpydoc - pip install sphinx_rtd_theme - - echo "Building docs" - cd docs - make clean - make html - - cd ../../ - echo "Setting git attributes" - git config --global user.email "fernandez@urbansim.com" - git config --global user.name "udst-documentator" - - echo "Cloning repository" - git clone --quiet --single-branch --branch=gh-pages https://${GH_TOKEN}@github.com/udst/urbanaccess.git gh-pages > /dev/null 2>&1 - - cd gh-pages - rm -rf * - cp -R ../urbanaccess/docs/build/html/* ./ - git add -A . - - git commit -am "Update dev docs after building $TRAVIS_BUILD_NUMBER" - echo "Pushing commit" - git push -fq origin gh-pages # > /dev/null 2>&1 -fi diff --git a/demo/simple_example.ipynb b/demo/simple_example.ipynb index a8088c1..79cb6f8 100644 --- a/demo/simple_example.ipynb +++ b/demo/simple_example.ipynb @@ -69,7 +69,7 @@ "- Loading a network from disk\n", "- Visualizing the network\n", "- Adding average headways to network travel time\n", - "- Using a UrbanAccess network with Pandana" + "- Using an UrbanAccess network with Pandana" ] }, { @@ -78,7 +78,13 @@ "metadata": {}, "outputs": [], "source": [ + "import matplotlib\n", + "matplotlib.use('agg') # allows notebook to be tested in Travis\n", + "\n", "import pandas as pd\n", + "import cartopy.crs as ccrs\n", + "import cartopy\n", + "import matplotlib.pyplot as plt\n", "import pandana as pdna\n", "import time\n", "\n", @@ -92,20 +98,6 @@ "%matplotlib inline" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [ - "# Pandana currently uses depreciated parameters in matplotlib, this hides the warning until its fixed\n", - "import warnings\n", - "import matplotlib.cbook\n", - "warnings.filterwarnings(\"ignore\",category=matplotlib.cbook.mplDeprecation)" - ] - }, { "cell_type": "markdown", "metadata": { @@ -143,9 +135,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "settings.log_console = True" @@ -163,9 +153,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "settings.log_console = False" @@ -503,9 +491,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "urbanaccess_net = ua.network.ua_network" @@ -1215,9 +1201,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "blocks_subset['node_id'] = transit_ped_net.get_node_ids(blocks_subset['x'], blocks_subset['y'])" @@ -1247,9 +1231,7 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "collapsed": true - }, + "metadata": {}, "outputs": [], "source": [ "transit_ped_net.set(blocks_subset.node_id, variable = blocks_subset.jobs, name='jobs')" @@ -1317,23 +1299,31 @@ { "cell_type": "code", "execution_count": null, - "metadata": { - "scrolled": true - }, + "metadata": {}, "outputs": [], "source": [ "s_time = time.time()\n", - "transit_ped_net.plot(jobs_15, \n", - " plot_type='scatter',\n", - " fig_kwargs={'figsize':[20,20]},\n", - " bmap_kwargs={'epsg':'26943','resolution':'h'},\n", - " plot_kwargs={'cmap':'gist_heat_r','s':4,'edgecolor':'none'})\n", + "\n", + "fig = plt.subplots(figsize=(20,20))\n", + "\n", + "data_crs = ccrs.PlateCarree()\n", + "ax = plt.axes(projection=ccrs.epsg(26943))\n", + "ax.add_feature(cartopy.feature.GSHHSFeature(scale='full'), edgecolor='grey')\n", + "\n", + "plt.scatter(transit_ped_net.nodes_df.x, transit_ped_net.nodes_df.y, \n", + " c=jobs_15, s=4, cmap='gist_heat_r', edgecolor='none', transform=data_crs)\n", + "cb = plt.colorbar()\n", + "\n", "print('Took {:,.2f} seconds'.format(time.time() - s_time))" ] }, { "cell_type": "markdown", - "metadata": {}, + "metadata": { + "pycharm": { + "name": "#%% md\n" + } + }, "source": [ "### Jobs accessible within 30 minutes" ] @@ -1345,11 +1335,17 @@ "outputs": [], "source": [ "s_time = time.time()\n", - "transit_ped_net.plot(jobs_30, \n", - " plot_type='scatter',\n", - " fig_kwargs={'figsize':[20,20]},\n", - " bmap_kwargs={'epsg':'26943','resolution':'h'},\n", - " plot_kwargs={'cmap':'gist_heat_r','s':4,'edgecolor':'none'})\n", + "\n", + "fig = plt.subplots(figsize=(20,20))\n", + "\n", + "data_crs = ccrs.PlateCarree()\n", + "ax = plt.axes(projection=ccrs.epsg(26943))\n", + "ax.add_feature(cartopy.feature.GSHHSFeature(scale='full'), edgecolor='grey')\n", + "\n", + "plt.scatter(transit_ped_net.nodes_df.x, transit_ped_net.nodes_df.y, \n", + " c=jobs_30, s=4, cmap='gist_heat_r', edgecolor='none', transform=data_crs)\n", + "cb = plt.colorbar()\n", + "\n", "print('Took {:,.2f} seconds'.format(time.time() - s_time))" ] }, @@ -1367,49 +1363,19 @@ "outputs": [], "source": [ "s_time = time.time()\n", - "transit_ped_net.plot(jobs_45, \n", - " plot_type='scatter',\n", - " fig_kwargs={'figsize':[20,20]},\n", - " bmap_kwargs={'epsg':'26943','resolution':'h'},\n", - " plot_kwargs={'cmap':'gist_heat_r','s':4,'edgecolor':'none'})\n", + "\n", + "fig = plt.subplots(figsize=(20,20))\n", + "\n", + "data_crs = ccrs.PlateCarree()\n", + "ax = plt.axes(projection=ccrs.epsg(26943))\n", + "ax.add_feature(cartopy.feature.GSHHSFeature(scale='full'), edgecolor='grey')\n", + "\n", + "plt.scatter(transit_ped_net.nodes_df.x, transit_ped_net.nodes_df.y, \n", + " c=jobs_45, s=4, cmap='gist_heat_r', edgecolor='none', transform=data_crs)\n", + "cb = plt.colorbar()\n", + "\n", "print('Took {:,.2f} seconds'.format(time.time() - s_time))" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { @@ -1433,4 +1399,4 @@ }, "nbformat": 4, "nbformat_minor": 1 -} +} \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..6b79ee2 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,31 @@ +This folder generates the UrbanAccess online documentation, hosted at https://udst.github.io/urbanaccess/. + +### How it works + +HTML files are generated using [Sphinx](http://sphinx-doc.org) and hosted with GitHub Pages from the `gh-pages` branch of the repository. The online documentation is rendered and updated **manually**. + +### Editing the documentation + +The files in `docs/source`, along with docstrings in the source code, determine what appears in the rendered documentation. Here's a [good tutorial](https://pythonhosted.org/an_example_pypi_project/sphinx.html) for Sphinx. + +### Previewing changes locally + +Install the copy of UrbanAccess that the documentation is meant to reflect. Install the documentation tools. + +``` +pip install . +pip install sphinx sphinx_rtd_theme numpydoc +``` + +Build the documentation. There should be status messages and warnings, but no errors. + +``` +cd docs +sphinx-build -b html source build +``` + +The HTML files will show up in `docs/build/`. + +### Uploading changes + +Clone a second copy of the repository and check out the `gh-pages` branch. Copy over the updated HTML files, commit them, and push the changes to GitHub. diff --git a/docs/build/.gitignore b/docs/build/.gitignore new file mode 100644 index 0000000..df3359d --- /dev/null +++ b/docs/build/.gitignore @@ -0,0 +1 @@ +*/* \ No newline at end of file diff --git a/docs/source/conf.py b/docs/source/conf.py index 38c052a..f365eb5 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -6,6 +6,7 @@ import sys import os import sphinx_rtd_theme +from datetime import datetime # -- General configuration ------------------------------------------------ @@ -23,15 +24,14 @@ 'sphinx.ext.autosummary' ] - templates_path = ['_templates'] source_suffix = '.rst' -master_doc = 'index' +master_doc = 'index' project = u'UrbanAccess' -copyright = u'2018, UrbanSim Inc.' author = u'UrbanSim Inc.' -version = u'0.2.0' -release = u'0.2.0' +copyright = u'{}, {}'.format(datetime.now().year, author) +version = u'0.2.1' +release = u'0.2.1' language = None # List of patterns to ignore when looking for source files. diff --git a/docs/source/gallery.rst b/docs/source/gallery.rst index 037c600..98ba214 100644 --- a/docs/source/gallery.rst +++ b/docs/source/gallery.rst @@ -24,10 +24,10 @@ Here are some examples of GTFS and OSM networks that have been created using Urb If you have interesting examples and would like us to share them here please contact us. .. |oakland| image:: _images/travel_time_net.png - :scale: 80% + :width: 782px .. |madison| image:: _images/madison.png - :scale: 80% + :width: 955px .. |sandiego| image:: _images/sandiego.png - :scale: 80% + :width: 956px .. |uk_rail| image:: _images/uk_rail.png - :scale: 80% \ No newline at end of file + :width: 778px \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index c3239e4..5b6133a 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,6 +3,8 @@ UrbanAccess A tool for computing GTFS transit and OSM pedestrian networks for accessibility analysis. +v0.2.1, released August 28, 2020. + Contents -------- diff --git a/docs/source/installation.rst b/docs/source/installation.rst index 9b577cc..f535577 100644 --- a/docs/source/installation.rst +++ b/docs/source/installation.rst @@ -17,32 +17,24 @@ Dependencies * pyyaml >= 3.11 * scikit-learn >= 0.17.1 -Dependencies can be installed through the ``conda-forge`` and ``udst`` channels. To add these as default installation channels for conda, run this code in a terminal:: - - conda config --add channels udst - conda config --add channels conda-forge - -Current status --------------- - -*Forthcoming improvements:* - -* Unit tests - Install the latest release -------------------------- conda ~~~~~~ -UrbanAccess is available on conda and can be installed with:: +UrbanAccess is available on Conda Forge and can be installed with:: + + conda install urbanaccess -c conda-forge + +If you'd like to permanently add ``conda-forge`` as a backup channel in your Conda figuration, you can do it like this:: - conda install -c udst urbanaccess + conda config --append channels conda-forge pip ~~~~~~ UrbanAccess is available on PyPI and can be installed with:: - pip install -U urbanaccess + pip install urbanaccess Development Installation ------------------------ diff --git a/docs/source/introduction.rst b/docs/source/introduction.rst index 2b23c1d..0f8e0ed 100644 --- a/docs/source/introduction.rst +++ b/docs/source/introduction.rst @@ -98,6 +98,6 @@ General workflow .. |travel_time_net| image:: _images/travel_time_net.png - :scale: 80% + :width: 782px .. _forum: http://discussion.urbansim.com/ \ No newline at end of file diff --git a/docs/source/pandana.rst b/docs/source/pandana.rst index 4ef8d59..3a6c0e7 100644 --- a/docs/source/pandana.rst +++ b/docs/source/pandana.rst @@ -63,12 +63,12 @@ Examples of a Pandana nearest POI query to hospitals and parks within a 30 minut .. |15min| image:: _images/15_min_jobs.png - :scale: 80% + :width: 709px .. |30min| image:: _images/30_min_jobs.png - :scale: 80% + :width: 714px .. |45min| image:: _images/45_min_jobs.png - :scale: 80% + :width: 714px .. |hospitals| image:: _images/hospitals_30.png - :scale: 80% + :width: 695px .. |parks| image:: _images/parks_30.png - :scale: 80% \ No newline at end of file + :width: 695px \ No newline at end of file diff --git a/docs/source/plot.rst b/docs/source/plot.rst index 18b51fa..9f6e54d 100644 --- a/docs/source/plot.rst +++ b/docs/source/plot.rst @@ -26,8 +26,8 @@ For example you can: .. |transit_net| image:: _images/transit_net.png - :scale: 80% + :width: 734px .. |ped_net| image:: _images/ped_net.png - :scale: 80% + :width: 734px .. |travel_time_net| image:: _images/travel_time_net.png - :scale: 80% \ No newline at end of file + :width: 734px \ No newline at end of file diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..fbaf9c8 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,14 @@ +# Additional requirements for development and testing + +# testing +pytest +pycodestyle + +# testing demo notebook +jupyter +cartopy # requires conda + +# building documentation +numpydoc +sphinx +sphinx_rtd_theme diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..e35b826 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,4 @@ +[pycodestyle] +# standard ignores, + E402 which is necessary for the Travis Matplotlib backend +ignore = E121,E123,E126,E133,E226,E241,E242,E402,E704,E503,W504,W505 +max-line-length = 100 diff --git a/setup.py b/setup.py index b254e44..1e75e96 100644 --- a/setup.py +++ b/setup.py @@ -1,14 +1,5 @@ -# Install setuptools if not installed. -try: - import setuptools -except ImportError: - from ez_setup import use_setuptools - - use_setuptools() - from setuptools import setup, find_packages - # read README as the long description with open('README.rst', 'r') as f: long_description = f.read() @@ -18,7 +9,7 @@ setup( name='urbanaccess', - version='0.2.0', + version='0.2.1', license='AGPL', description=description, long_description=long_description, @@ -30,6 +21,8 @@ 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', 'Development Status :: 3 - Alpha', 'License :: OSI Approved :: GNU Affero General Public License v3' ], diff --git a/urbanaccess/__init__.py b/urbanaccess/__init__.py index 4c16e9e..c45aee2 100644 --- a/urbanaccess/__init__.py +++ b/urbanaccess/__init__.py @@ -9,6 +9,6 @@ from .gtfsfeeds import * from .plot import * -__version__ = "0.2.0" +__version__ = "0.2.1" version = __version__ diff --git a/urbanaccess/gtfs/utils_validation.py b/urbanaccess/gtfs/utils_validation.py index 69029b3..b31c4f1 100644 --- a/urbanaccess/gtfs/utils_validation.py +++ b/urbanaccess/gtfs/utils_validation.py @@ -83,7 +83,7 @@ def _boundingbox_check(df, feed_folder, lat_min=None, lng_min=None, len(df), (len(outside_boundingbox) / len(df)) * 100), level=lg.WARNING) if verbose: - log('Records: {}') + log('Records:') log('{}'.format(outside_boundingbox)) if remove: df_subset = df.drop(outside_boundingbox.index) diff --git a/urbanaccess/network.py b/urbanaccess/network.py index 255a043..849afdc 100644 --- a/urbanaccess/network.py +++ b/urbanaccess/network.py @@ -1,12 +1,20 @@ import time +import geopy +from geopy import distance + from sklearn.neighbors import KDTree import pandas as pd -from geopy.distance import vincenty from urbanaccess.utils import log, df_to_hdf5, hdf5_to_df from urbanaccess import config +if int(geopy.__version__[0]) < 2: + dist_calc = distance.vincenty +else: + dist_calc = distance.geodesic + + class urbanaccess_network(object): """ A urbanaccess object of Pandas DataFrames representing @@ -61,9 +69,14 @@ def _nearest_neighbor(df1, df2): df1.index.values[indexes] : pandas.Series index of records in df1 that are nearest to the coordinates in df2 """ - - kdt = KDTree(df1.as_matrix()) - indexes = kdt.query(df2.as_matrix(), k=1, return_distance=False) + try: + df1_matrix = df1.to_numpy() + df2_matrix = df2.to_numpy() + except AttributeError: + df1_matrix = df1.values + df2_matrix = df2.values + kdt = KDTree(df1_matrix) + indexes = kdt.query(df2_matrix, k=1, return_distance=False) return df1.index.values[indexes] @@ -379,8 +392,8 @@ def _connector_edges(osm_nodes, transit_nodes, travel_speed_mph=3): osm_node_id = int(row['nearest_osm_node']) osm_row = osm_nodes.loc[osm_node_id] - distance = vincenty((row['y'], row['x']), - (osm_row['y'], osm_row['x'])).miles + distance = dist_calc((row['y'], row['x']), + (osm_row['y'], osm_row['x'])).miles time_ped_to_transit = distance / travel_speed_mph * 60 time_transit_to_ped = distance / travel_speed_mph * 60 diff --git a/urbanaccess/tests/integration/integration_madison.py b/urbanaccess/tests/integration/integration_madison.py index 9ebeaf9..8e1c114 100644 --- a/urbanaccess/tests/integration/integration_madison.py +++ b/urbanaccess/tests/integration/integration_madison.py @@ -1,6 +1,9 @@ import os import time +import matplotlib +matplotlib.use('agg') + import urbanaccess from urbanaccess.gtfsfeeds import feeds diff --git a/urbanaccess/tests/integration/integration_sandiego.py b/urbanaccess/tests/integration/integration_sandiego.py index 1342164..e6459a1 100644 --- a/urbanaccess/tests/integration/integration_sandiego.py +++ b/urbanaccess/tests/integration/integration_sandiego.py @@ -2,6 +2,9 @@ import time import pandas as pd +import matplotlib +matplotlib.use('agg') + import urbanaccess start_time = time.time() diff --git a/urbanaccess/tests/test_network.py b/urbanaccess/tests/test_network.py new file mode 100644 index 0000000..c0f5521 --- /dev/null +++ b/urbanaccess/tests/test_network.py @@ -0,0 +1,85 @@ +import pytest +import pandas as pd +from urbanaccess import network + + +@pytest.fixture +def osm_nodes_df(): + data = { + 'id': (1, 2, 3), + 'x': [-122.267546, -122.264479, -122.219119], + 'y': [37.802919, 37.808042, 37.782288] + } + osm_nodes = pd.DataFrame(data).set_index('id') + return osm_nodes + + +@pytest.fixture +def transit_nodes_df(): + data = { + 'node_id_route': ['1_transit_a', '2_transit_a', + '3_transit_a', '4_transit_a'], + 'x': [-122.265417, -122.266910, -122.269741, -122.238638], + 'y': [37.806372, 37.802687, 37.799480, 37.797234] + } + transit_nodes = pd.DataFrame(data).set_index('node_id_route') + return transit_nodes + + +@pytest.fixture +def expected_transit_nodes_neighbor_df(transit_nodes_df): + data = {'node_id_route': ['1_transit_a', '2_transit_a', + '3_transit_a', '4_transit_a'], + 'nearest_osm_node': [2, 1, 1, 3]} + index = range(4) + expected_transit_nodes = pd.concat( + [transit_nodes_df, + pd.DataFrame(data, index).set_index('node_id_route')], + axis=1) + return expected_transit_nodes + + +@pytest.fixture +def expected_connector_edge_df(): + data = {'from': ['1_transit_a', 2, + '2_transit_a', 1, + '3_transit_a', 1, + '4_transit_a', 3], + 'to': [2, '1_transit_a', + 1, '2_transit_a', + 1, '3_transit_a', + 3, '4_transit_a'], + 'weight': [2.521901, 2.521901, 0.766106, 0.766106, + 5.317242, 5.317242, 29.690540, 29.690540], + 'net_type': ['transit to osm', 'osm to transit', + 'transit to osm', 'osm to transit', + 'transit to osm', 'osm to transit', + 'transit to osm', 'osm to transit']} + + index = range(8) + expected_connector_edge = pd.DataFrame(data, index) + return expected_connector_edge + + +def test_nearest_neighbor(osm_nodes_df, transit_nodes_df, + expected_transit_nodes_neighbor_df): + transit_nodes_df['nearest_osm_node'] = network._nearest_neighbor( + osm_nodes_df[['x', 'y']], + transit_nodes_df[['x', 'y']]) + + assert expected_transit_nodes_neighbor_df.equals(transit_nodes_df) + + +def test_connector_edges(osm_nodes_df, transit_nodes_df, + expected_connector_edge_df): + net_connector_edges = network._connector_edges(osm_nodes_df, + transit_nodes_df, + travel_speed_mph=3) + net_connector_edges['weight'] = net_connector_edges['weight'].round(6) + expected_connector_edge_df['weight'] = \ + expected_connector_edge_df['weight'].round(6) + + col_order = ['from', 'to', 'weight', 'net_type'] + expected_connector_edge_df = expected_connector_edge_df[col_order] + net_connector_edges = net_connector_edges[col_order] + assert expected_connector_edge_df.equals(net_connector_edges)