├── .gitattributes ├── .github ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.LOCAL-ANALYSIS.md ├── README.md ├── common.yml ├── data └── .gitkeep ├── deployment ├── README.md ├── aws-batch │ ├── Dockerfile │ ├── docker-compose.yml │ ├── job-definitions │ │ ├── production-pfb-analysis-run-job.json │ │ └── staging-pfb-analysis-run-job.json │ ├── requirements.txt │ └── update_job_definition.py └── terraform │ ├── .gitconfig │ ├── alarms.tf │ ├── app-container-service.tf │ ├── app.tf │ ├── batch-container-service.tf │ ├── cdn.tf │ ├── cloud-config │ ├── ecs-batch-user-data.yml │ └── ecs-user-data.yml │ ├── config.tf │ ├── database.tf │ ├── dns.tf │ ├── docker-compose.yml │ ├── firewall.tf │ ├── iam.tf │ ├── network.tf │ ├── storage.tf │ ├── task-definitions │ ├── app.json │ ├── django-q.json │ ├── management.json │ └── nginx.json │ ├── variables.tf │ └── versions.tf ├── docker-compose.test.yml ├── docker-compose.yml ├── docs ├── SystemComponentsOverview.md └── architecture │ ├── adr-0000-architecture-documentation.md │ ├── adr-0001-development-environment.md │ ├── adr-0002-backend-language-framework.md │ ├── adr-0003-asynchronous-task-queue.md │ └── adr-0004-asynchronous-task-queue-2.md ├── prototype ├── Gruntfile.js ├── Readme.md ├── app │ ├── assets │ │ ├── css │ │ │ ├── .gitkeep │ │ │ ├── main.css │ │ │ └── main.css.map │ │ ├── fonts │ │ │ ├── fontello-old │ │ │ │ ├── config.json │ │ │ │ └── font │ │ │ │ │ ├── mantle.eot │ │ │ │ │ ├── mantle.svg │ │ │ │ │ ├── mantle.ttf │ │ │ │ │ ├── mantle.woff │ │ │ │ │ └── mantle.woff2 │ │ │ └── fontello │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── config.json │ │ │ │ ├── css │ │ │ │ └── fontello.css │ │ │ │ └── font │ │ │ │ ├── fontello.eot │ │ │ │ ├── fontello.svg │ │ │ │ ├── fontello.ttf │ │ │ │ ├── fontello.woff │ │ │ │ └── fontello.woff2 │ │ ├── images │ │ │ ├── .gitkeep │ │ │ ├── footer-stripes.png │ │ │ ├── home-header-2.jpg │ │ │ ├── home-header-3.jpg │ │ │ ├── home-header.jpg │ │ │ ├── placesforbikes-icon.png │ │ │ ├── placesforbikes-logo-vertical.png │ │ │ ├── placesforbikes-logo-white.png │ │ │ └── placesforbikes-logo.png │ │ ├── js │ │ │ ├── .gitkeep │ │ │ ├── bootstrap.min.js │ │ │ └── main.js │ │ └── sass │ │ │ ├── base │ │ │ ├── _animation.scss │ │ │ ├── _base.scss │ │ │ ├── _grid.scss │ │ │ ├── _helpers.scss │ │ │ ├── _list.scss │ │ │ ├── _normalize.scss │ │ │ └── _typography.scss │ │ │ ├── components │ │ │ ├── _button.scss │ │ │ ├── _card.scss │ │ │ ├── _dropdown.scss │ │ │ ├── _form.scss │ │ │ ├── _image.scss │ │ │ ├── _map.scss │ │ │ ├── _metrics.scss │ │ │ ├── _nav.scss │ │ │ ├── _network-score.scss │ │ │ └── _tooltip.scss │ │ │ ├── layout │ │ │ ├── _container.scss │ │ │ ├── _footer.scss │ │ │ ├── _header.scss │ │ │ ├── _navbar.scss │ │ │ └── _section.scss │ │ │ ├── main.scss │ │ │ ├── pages │ │ │ ├── _compare.scss │ │ │ ├── _home.scss │ │ │ ├── _location.scss │ │ │ └── _mapping.scss │ │ │ └── utils │ │ │ ├── _mixins.scss │ │ │ └── _variables.scss │ ├── compare.html │ ├── index.html │ ├── search.html │ └── town.html ├── grunt │ ├── aliases.yaml │ ├── browserSync.js │ ├── concurrent.js │ ├── postcss.js │ ├── sass.js │ └── watch.js └── package.json ├── scripts ├── cibuild ├── cipublish ├── clean-analysis-volumes ├── clear-tile-cache ├── console ├── django-manage ├── infra ├── run-local-analysis ├── server ├── setup ├── test └── update ├── src ├── analysis │ ├── .gitignore │ ├── Dockerfile │ ├── Dockerfile.remote-db │ ├── LICENSE │ ├── README.md │ ├── connectivity │ │ ├── access_colleges.sql │ │ ├── access_community_centers.sql │ │ ├── access_dentists.sql │ │ ├── access_doctors.sql │ │ ├── access_hospitals.sql │ │ ├── access_jobs.sql │ │ ├── access_overall.sql │ │ ├── access_parks.sql │ │ ├── access_pharmacies.sql │ │ ├── access_population.sql │ │ ├── access_retail.sql │ │ ├── access_schools.sql │ │ ├── access_social_services.sql │ │ ├── access_supermarkets.sql │ │ ├── access_trails.sql │ │ ├── access_transit.sql │ │ ├── access_universities.sql │ │ ├── build_network.sql │ │ ├── census_block_jobs.sql │ │ ├── census_blocks.sql │ │ ├── connected_census_blocks.sql │ │ ├── destinations │ │ │ ├── colleges.sql │ │ │ ├── community_centers.sql │ │ │ ├── dentists.sql │ │ │ ├── doctors.sql │ │ │ ├── hospitals.sql │ │ │ ├── parks.sql │ │ │ ├── pharmacies.sql │ │ │ ├── retail.sql │ │ │ ├── schools.sql │ │ │ ├── social_services.sql │ │ │ ├── supermarkets.sql │ │ │ ├── transit.sql │ │ │ └── universities.sql │ │ ├── overall_scores.sql │ │ ├── reachable_roads_high_stress_calc.sql │ │ ├── reachable_roads_high_stress_cleanup.sql │ │ ├── reachable_roads_high_stress_prep.sql │ │ ├── reachable_roads_low_stress_calc.sql │ │ ├── reachable_roads_low_stress_cleanup.sql │ │ ├── reachable_roads_low_stress_prep.sql │ │ └── score_inputs.sql │ ├── features │ │ ├── bike_infra.sql │ │ ├── class_adjustments.sql │ │ ├── functional_class.sql │ │ ├── island.sql │ │ ├── lanes.sql │ │ ├── legs.sql │ │ ├── one_way.sql │ │ ├── park.sql │ │ ├── paths.sql │ │ ├── rrfb.sql │ │ ├── signalized.sql │ │ ├── speed_limit.sql │ │ ├── stops.sql │ │ ├── streetlight │ │ │ ├── streetlight_destinations.sql │ │ │ └── streetlight_gates.sql │ │ └── width_ft.sql │ ├── import │ │ ├── clip_osm.sql │ │ ├── import_jobs.sh │ │ ├── import_neighborhood.sh │ │ ├── import_osm.sh │ │ ├── mapconfig_cycleway.xml │ │ ├── mapconfig_highway.xml │ │ ├── pfb.style │ │ └── prepare_tables.sql │ ├── scripts │ │ ├── country_to_continent.py │ │ ├── detect_utm_zone.py │ │ ├── download_osm_extract.py │ │ ├── drop_tables.sh │ │ ├── entrypoint.sh │ │ ├── export_connectivity.sh │ │ ├── import.sh │ │ ├── run_analysis.sh │ │ ├── run_connectivity.sh │ │ ├── setup_database.sh │ │ └── utils.sh │ └── stress │ │ ├── stress_lesser_ints.sql │ │ ├── stress_link_ints.sql │ │ ├── stress_living_street.sql │ │ ├── stress_motorway-trunk.sql │ │ ├── stress_motorway-trunk_ints.sql │ │ ├── stress_one_way_reset.sql │ │ ├── stress_path.sql │ │ ├── stress_primary_ints.sql │ │ ├── stress_secondary_ints.sql │ │ ├── stress_segments_higher_order.sql │ │ ├── stress_segments_lower_order.sql │ │ ├── stress_segments_lower_order_res.sql │ │ ├── stress_tertiary_ints.sql │ │ └── stress_track.sql ├── angularjs │ ├── .bowerrc │ ├── .editorconfig │ ├── .eslintrc │ ├── .gitignore │ ├── Dockerfile │ ├── bower.json │ ├── gulp │ │ ├── .eslintrc │ │ ├── build.js │ │ ├── conf.js │ │ ├── docs.js │ │ ├── inject.js │ │ ├── scripts.js │ │ ├── server.js │ │ ├── styles.js │ │ └── watch.js │ ├── gulpfile.js │ ├── package.json │ └── src │ │ ├── app │ │ ├── analysis-jobs │ │ │ ├── analysis-jobs.constants.js │ │ │ ├── create │ │ │ │ ├── analysis-jobs-create-batch.controller.js │ │ │ │ ├── analysis-jobs-create-batch.html │ │ │ │ ├── analysis-jobs-create.controller.js │ │ │ │ ├── analysis-jobs-create.html │ │ │ │ └── module.js │ │ │ ├── detail │ │ │ │ ├── analysis-jobs-detail.controller.js │ │ │ │ ├── analysis-jobs-detail.html │ │ │ │ └── module.js │ │ │ ├── import │ │ │ │ ├── analysis-jobs-import.controller.js │ │ │ │ ├── analysis-jobs-import.html │ │ │ │ └── module.js │ │ │ ├── list │ │ │ │ ├── analysis-jobs-list.controller.js │ │ │ │ ├── analysis-jobs-list.html │ │ │ │ └── module.js │ │ │ └── module.js │ │ ├── components │ │ │ ├── analysis-jobs │ │ │ │ ├── analysis-jobs-status.filter.js │ │ │ │ ├── analysis-jobs.service.js │ │ │ │ └── module.js │ │ │ ├── auth │ │ │ │ ├── auth.service.js │ │ │ │ ├── module.js │ │ │ │ ├── password.service.js │ │ │ │ └── token.service.js │ │ │ ├── countries.service.js │ │ │ ├── filters │ │ │ │ ├── analysis-job-filter.directive.js │ │ │ │ ├── analysis-job-filter.html │ │ │ │ ├── module.js │ │ │ │ ├── neighborhood-filter.directive.js │ │ │ │ ├── neighborhood-filter.html │ │ │ │ ├── user-filter.directive.js │ │ │ │ └── user-filter.html │ │ │ ├── footer │ │ │ │ ├── footer.directive.js │ │ │ │ └── footer.html │ │ │ ├── map │ │ │ │ ├── map.constants.js │ │ │ │ ├── map.directive.js │ │ │ │ └── module.js │ │ │ ├── modals │ │ │ │ ├── confirmation-modal.html │ │ │ │ ├── confirmation-modal.js │ │ │ │ ├── modal-instance-controller.js │ │ │ │ └── module.js │ │ │ ├── module.js │ │ │ ├── navbar │ │ │ │ ├── navbar.directive.js │ │ │ │ └── navbar.html │ │ │ ├── neighborhoods.service.js │ │ │ ├── organizations.service.js │ │ │ ├── pagination.service.js │ │ │ ├── places.service.js │ │ │ ├── scoremetadata.service.js │ │ │ ├── thumbnail-map │ │ │ │ ├── module.js │ │ │ │ ├── thumbnail-map.directive.js │ │ │ │ └── thumbnail-map.html │ │ │ └── users.service.js │ │ ├── help │ │ │ ├── help.controller.js │ │ │ ├── help.html │ │ │ └── module.js │ │ ├── home │ │ │ ├── home.controller.js │ │ │ ├── home.html │ │ │ ├── module.js │ │ │ ├── neighborhood-map.directive.js │ │ │ └── neighborhood-map.html │ │ ├── index.config.js │ │ ├── index.constants.js │ │ ├── index.module.js │ │ ├── index.route.js │ │ ├── index.run.js │ │ ├── leaflet │ │ │ ├── map-button.control.js │ │ │ ├── map-legend.control.js │ │ │ └── map-speed-limit-legend.control.js │ │ ├── login │ │ │ ├── login.html │ │ │ ├── module.js │ │ │ └── user-login.controller.js │ │ ├── neighborhoods │ │ │ ├── detail │ │ │ │ ├── module.js │ │ │ │ ├── neighborhood-detail-map.directive.js │ │ │ │ ├── neighborhood-detail-map.html │ │ │ │ ├── neighborhoods-detail.controller.js │ │ │ │ └── neighborhoods-detail.html │ │ │ ├── list │ │ │ │ ├── module.js │ │ │ │ ├── neighborhoods-list.controller.js │ │ │ │ └── neighborhoods-list.html │ │ │ └── module.js │ │ ├── organizations │ │ │ ├── detail │ │ │ │ ├── module.js │ │ │ │ ├── organizations-detail.controller.js │ │ │ │ └── organizations-detail.html │ │ │ ├── list │ │ │ │ ├── module.js │ │ │ │ ├── organizations-list.controller.js │ │ │ │ └── organizations-list.html │ │ │ └── module.js │ │ ├── password-reset │ │ │ ├── module.js │ │ │ ├── request │ │ │ │ ├── module.js │ │ │ │ ├── password-reset-request.controller.js │ │ │ │ └── password-reset-request.html │ │ │ └── reset │ │ │ │ ├── module.js │ │ │ │ ├── password-reset.controller.js │ │ │ │ └── password-reset.html │ │ ├── places │ │ │ ├── compare │ │ │ │ ├── compare.controller.js │ │ │ │ ├── compare.html │ │ │ │ └── module.js │ │ │ ├── detail │ │ │ │ ├── module.js │ │ │ │ ├── place-map.directive.js │ │ │ │ ├── place-map.html │ │ │ │ ├── places-detail.controller.js │ │ │ │ └── places-detail.html │ │ │ ├── list │ │ │ │ ├── module.js │ │ │ │ ├── place-list.html │ │ │ │ ├── places-list-map-directive.js │ │ │ │ ├── places-list-map.html │ │ │ │ └── places-list.controller.js │ │ │ └── module.js │ │ ├── users │ │ │ ├── detail │ │ │ │ ├── module.js │ │ │ │ ├── users-detail.controller.js │ │ │ │ └── users-detail.html │ │ │ ├── list │ │ │ │ ├── module.js │ │ │ │ ├── users-list.controller.js │ │ │ │ └── users-list.html │ │ │ └── module.js │ │ └── utils │ │ │ ├── module.js │ │ │ └── parse-errors.js │ │ ├── assets │ │ ├── fonts │ │ │ ├── bootstrap │ │ │ │ ├── glyphicons-halflings-regular.eot │ │ │ │ ├── glyphicons-halflings-regular.svg │ │ │ │ ├── glyphicons-halflings-regular.ttf │ │ │ │ ├── glyphicons-halflings-regular.woff │ │ │ │ └── glyphicons-halflings-regular.woff2 │ │ │ └── fontello │ │ │ │ ├── LICENSE.txt │ │ │ │ ├── config.json │ │ │ │ ├── css │ │ │ │ └── fontello.css │ │ │ │ └── font │ │ │ │ ├── fontello.eot │ │ │ │ ├── fontello.svg │ │ │ │ ├── fontello.ttf │ │ │ │ ├── fontello.woff │ │ │ │ └── fontello.woff2 │ │ └── images │ │ │ ├── .gitkeep │ │ │ ├── bna-icon-dark.png │ │ │ ├── bna-icon.png │ │ │ ├── bna-logo-2021.png │ │ │ ├── bna-logo-horizontal-2021.png │ │ │ ├── bna-logo.png │ │ │ ├── fatality-active-icon.png │ │ │ ├── fatality-bike-icon.png │ │ │ ├── fatality-motor-icon.png │ │ │ ├── favicon.ico │ │ │ ├── home-header-2.jpg │ │ │ ├── home-header-3.jpg │ │ │ ├── home-header.jpg │ │ │ ├── people-for-bikes-icon.svg │ │ │ ├── people-for-bikes-logo.svg │ │ │ └── web-racing-stripe.png │ │ ├── index.html │ │ └── styles │ │ ├── base │ │ ├── _animation.scss │ │ ├── _base.scss │ │ ├── _grid.scss │ │ ├── _helpers.scss │ │ ├── _list.scss │ │ ├── _normalize.scss │ │ ├── _print.scss │ │ └── _typography.scss │ │ ├── components │ │ ├── _button.scss │ │ ├── _card.scss │ │ ├── _dropdown.scss │ │ ├── _form.scss │ │ ├── _image.scss │ │ ├── _map.scss │ │ ├── _metrics.scss │ │ ├── _nav.scss │ │ ├── _network-score.scss │ │ ├── _panel.scss │ │ ├── _table.scss │ │ └── _tooltip.scss │ │ ├── index.scss │ │ ├── layout │ │ ├── _container.scss │ │ ├── _footer.scss │ │ ├── _header.scss │ │ ├── _navbar.scss │ │ └── _section.scss │ │ ├── pages │ │ ├── _compare.scss │ │ ├── _home.scss │ │ ├── _location.scss │ │ └── _mapping.scss │ │ └── utils │ │ ├── _mixins.scss │ │ └── _variables.scss ├── django │ ├── Dockerfile │ ├── gunicorn.conf.py │ ├── manage.py │ ├── pfb_analysis │ │ ├── __init__.py │ │ ├── apps.py │ │ ├── aws_batch.py │ │ ├── countries.py │ │ ├── filters.py │ │ ├── fixtures │ │ │ └── analysis-score-metadata.json │ │ ├── functions.py │ │ ├── management │ │ │ ├── __init__.py │ │ │ └── commands │ │ │ │ ├── __init__.py │ │ │ │ ├── add_city_fips.py │ │ │ │ ├── analysis_batch_cancel.py │ │ │ │ ├── analysis_batch_create.py │ │ │ │ ├── generate_analysis_csv.py │ │ │ │ ├── import_crash_data.py │ │ │ │ ├── import_results_shapefiles.py │ │ │ │ ├── load_overall_scores.py │ │ │ │ ├── load_residential_speed_limit.py │ │ │ │ ├── run_analysis_job.py │ │ │ │ ├── set_job_attr.py │ │ │ │ └── update_status.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_analysisjob_batch_job_id.py │ │ │ ├── 0002_auto_20170307_1640.py │ │ │ ├── 0003_merge_20170307_1930.py │ │ │ ├── 0004_analysisjob_osm_extract_url.py │ │ │ ├── 0004_analysisjobstatusupdate.py │ │ │ ├── 0004_auto_20170317_2018.py │ │ │ ├── 0005_remove_analysisjob_status.py │ │ │ ├── 0006_merge_20170321_1656.py │ │ │ ├── 0006_merge_20170322_1647.py │ │ │ ├── 0007_auto_20170322_1309.py │ │ │ ├── 0008_merge_20170323_1200.py │ │ │ ├── 0009_auto_20170323_1535.py │ │ │ ├── 0010_analysisjob_overall_scores.py │ │ │ ├── 0011_analysisjob_census_block_count.py │ │ │ ├── 0012_analysisscoremetadata.py │ │ │ ├── 0013_auto_20170407_0106.py │ │ │ ├── 0014_auto_20170412_1611.py │ │ │ ├── 0014_neighborhood_geom.py │ │ │ ├── 0015_add_neighborhood_geoms.py │ │ │ ├── 0016_merge_20170412_1804.py │ │ │ ├── 0017_neighborhood_geom_pt.py │ │ │ ├── 0018_add_neighborhood_geom_pt.py │ │ │ ├── 0019_auto_20170421_1746.py │ │ │ ├── 0020_neighborhood_geom_simple.py │ │ │ ├── 0021_neighborhood_visibility.py │ │ │ ├── 0022_neighborhood_last_job.py │ │ │ ├── 0023_auto_20170509_2110.py │ │ │ ├── 0024_auto_20170509_2121.py │ │ │ ├── 0025_auto_20170511_1244.py │ │ │ ├── 0026_auto_20170517_1531.py │ │ │ ├── 0027_remove_states_from_labels.py │ │ │ ├── 0028_analysisscoremetadata_priority.py │ │ │ ├── 0029_auto_20170802_1425.py │ │ │ ├── 0030_auto_20180419_1342.py │ │ │ ├── 0031_censusblocksresults_neighborhoodwaysresults.py │ │ │ ├── 0032_neighborhood_country.py │ │ │ ├── 0033_auto_20181205_1913.py │ │ │ ├── 0034_add_analysislocaluploadtask_model.py │ │ │ ├── 0035_auto_20190124_1853.py │ │ │ ├── 0036_neighborhood_city_fips.py │ │ │ ├── 0037_auto_20190227_2110.py │ │ │ ├── 0038_move_territories_to_us.py │ │ │ ├── 0039_alter_neighborhood_state_abbrev_field.py │ │ │ ├── 0040_neighborhood_analysis_job_ondelete.py │ │ │ ├── 0041_auto_20201006_2320.py │ │ │ ├── 0042_analysisjob_max_trip_distance.py │ │ │ ├── 0043_update_jsonfield_django_32.py │ │ │ ├── 0044_analysisjob_population_url.py │ │ │ ├── 0045_analysisjob_skip_import_jobs.py │ │ │ ├── 0046_analysisjob_jobs_url_field.py │ │ │ ├── 0047_neighborhood_speed_limit.py │ │ │ ├── 0048_crash.py │ │ │ ├── 0049_neighborhood_geog.py │ │ │ ├── 0050_update_pfb_analysis_and_user.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── tasks.py │ │ ├── tests │ │ │ ├── __init__.py │ │ │ ├── data │ │ │ │ ├── ann-arbor-mi │ │ │ │ │ ├── ann-arbor-mi.cpg │ │ │ │ │ ├── ann-arbor-mi.dbf │ │ │ │ │ ├── ann-arbor-mi.prj │ │ │ │ │ ├── ann-arbor-mi.shp │ │ │ │ │ └── ann-arbor-mi.shx │ │ │ │ ├── batch_create_shapefile │ │ │ │ │ ├── PFB_BigJumpCities_1.dbf │ │ │ │ │ ├── PFB_BigJumpCities_1.prj │ │ │ │ │ ├── PFB_BigJumpCities_1.qpj │ │ │ │ │ ├── PFB_BigJumpCities_1.shp │ │ │ │ │ ├── PFB_BigJumpCities_1.shx │ │ │ │ │ └── PFB_BigJumpCities_1.zip │ │ │ │ └── birmingham │ │ │ │ │ ├── birmingham.cpg │ │ │ │ │ ├── birmingham.dbf │ │ │ │ │ ├── birmingham.prj │ │ │ │ │ ├── birmingham.shp │ │ │ │ │ └── birmingham.shx │ │ │ └── test_models.py │ │ └── views.py │ ├── pfb_network_connectivity │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── filters.py │ │ ├── migrations │ │ │ ├── 0001_initial.py │ │ │ ├── 0002_delete_neighborhood.py │ │ │ └── __init__.py │ │ ├── models.py │ │ ├── pagination.py │ │ ├── permissions.py │ │ ├── serializers.py │ │ ├── settings.py │ │ ├── tests.py │ │ ├── urls.py │ │ ├── utils.py │ │ ├── views.py │ │ └── wsgi.py │ ├── requirements.txt │ ├── setup.cfg │ └── users │ │ ├── __init__.py │ │ ├── admin.py │ │ ├── apps.py │ │ ├── emails.py │ │ ├── migrations │ │ ├── 0001_initial.py │ │ ├── 0002_auto_20170221_1653.py │ │ ├── 0003_auto_20170224_1639.py │ │ ├── 0004_auto_20170509_1559.py │ │ ├── 0005_update_pfb_analysis_and_user.py │ │ └── __init__.py │ │ ├── models.py │ │ ├── serializers.py │ │ ├── tests.py │ │ ├── utils.py │ │ └── views.py ├── nginx │ ├── Dockerfile │ ├── etc │ │ └── nginx │ │ │ ├── conf.d │ │ │ └── default.conf │ │ │ └── nginx.conf │ └── srv │ │ ├── dist │ │ └── .gitkeep │ │ └── static │ │ └── .gitkeep ├── tilegarden │ ├── .env.dev │ ├── .env.example │ ├── .eslintrc.json │ ├── .gitignore │ ├── Dockerfile │ ├── package.json │ ├── scripts │ │ ├── build-all-xml.sh │ │ ├── build-xml.sh │ │ ├── deploy │ │ ├── deploy-new │ │ └── template-vars.js │ ├── src │ │ ├── api.js │ │ ├── config │ │ │ ├── bike_infrastructure.xml │ │ │ ├── census_blocks.xml │ │ │ ├── mml │ │ │ │ ├── bike_infrastructure.mml │ │ │ │ ├── bike_infrastructure.mss │ │ │ │ ├── census_blocks.mml │ │ │ │ ├── census_blocks.mss │ │ │ │ ├── ways.mml │ │ │ │ └── ways.mss │ │ │ └── ways.xml │ │ ├── tiler.js │ │ └── util │ │ │ ├── bounding-box.js │ │ │ ├── error-builder.js │ │ │ ├── logger.js │ │ │ ├── param-filter.js │ │ │ └── xml-tools.js │ ├── tests │ │ ├── api.test.js │ │ ├── bounding-box.test.js │ │ └── error-builder.test.js │ └── yarn.lock └── verifier │ ├── Dockerfile │ ├── compare_outputs.sh │ ├── docker-compose.yml │ └── requirements.txt └── verified_output └── boulder.csv /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.png binary 3 | *.jpg binary 4 | *.ttf binary 5 | *.shp binary 6 | *.shx binary 7 | *.zip binary 8 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Overview 2 | 3 | Brief description of what this PR does, and why it is needed. 4 | 5 | 6 | ### Demo 7 | 8 | Optional. Screenshots, `curl` examples, etc. 9 | 10 | 11 | ### Notes 12 | 13 | Optional. Ancillary topics, caveats, alternative strategies that didn't work out, anything else. 14 | 15 | 16 | ## Testing Instructions 17 | 18 | * How to test this PR 19 | * Prefer bulleted description 20 | * Start after checking out this branch 21 | * Include any setup required, such as bundling scripts, restarting services, etc. 22 | * Include test case, and expected output 23 | 24 | 25 | ## Checklist 26 | 27 | - [ ] Add entry to CHANGELOG.md 28 | 29 | Resolves #XXX 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Azavea, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /common.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | base: 4 | environment: 5 | - DJANGO_ENV=development 6 | - DJANGO_LOG_LEVEL=INFO 7 | - AWS_PROFILE=pfb 8 | - PFB_DB_HOST=database.service.pfb.internal 9 | - PFB_DB_DATABASE=pfb 10 | - PFB_DB_PASSWORD=pfb 11 | - PFB_DB_PORT=5432 12 | - PFB_DB_USER=pfb 13 | - PFB_SECRET_KEY=secret 14 | - PFB_AWS_BATCH_ANALYSIS_JOB_QUEUE_NAME=dummy-test-pfb-analysis-job-queue 15 | - PFB_AWS_BATCH_ANALYSIS_JOB_DEFINITION_NAME_REVISION="dummy-test-pfb-analysis-run-job:1" 16 | - PFB_AWS_BATCH_ANALYSIS_JOB_DEFINITION_NAME=dummy-test-pfb-analysis-run-job 17 | - PFB_TILEGARDEN_ROOT=http://localhost:9400 18 | - PFB_TILEGARDEN_CACHE_BUCKET=dev-pfb-tilecache-us-east-1 19 | volumes: 20 | - $HOME/.aws:/root/.aws:ro 21 | 22 | django-common: 23 | extends: 24 | service: base 25 | build: 26 | context: ./src/django 27 | dockerfile: Dockerfile 28 | -------------------------------------------------------------------------------- /data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/data/.gitkeep -------------------------------------------------------------------------------- /deployment/aws-batch/Dockerfile: -------------------------------------------------------------------------------- 1 | 2 | FROM python:3.9-slim 3 | 4 | MAINTAINER Azavea 5 | 6 | RUN apt-get update && apt-get install -y --no-install-recommends \ 7 | python3-pip \ 8 | build-essential \ 9 | && rm -rf /var/lib/apt/lists/* 10 | 11 | COPY requirements.txt /tmp/ 12 | RUN pip3 install --upgrade pip && pip3 install --no-cache-dir -r /tmp/requirements.txt 13 | 14 | COPY ./ /opt/aws-batch 15 | 16 | WORKDIR /opt/aws-batch 17 | 18 | ENTRYPOINT ["python", "update_job_definition.py"] 19 | 20 | CMD ["--help"] 21 | -------------------------------------------------------------------------------- /deployment/aws-batch/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | update-job-defs: 3 | image: update-job-defs 4 | build: 5 | context: . 6 | dockerfile: Dockerfile 7 | environment: 8 | - AWS_PROFILE 9 | - AWS_DEFAULT_REGION 10 | - AWS_ACCESS_KEY_ID 11 | - AWS_SECRET_ACCESS_KEY 12 | volumes: 13 | - $HOME/.aws:/root/.aws:ro 14 | -------------------------------------------------------------------------------- /deployment/aws-batch/requirements.txt: -------------------------------------------------------------------------------- 1 | boto3==1.26.132 2 | -------------------------------------------------------------------------------- /deployment/terraform/.gitconfig: -------------------------------------------------------------------------------- 1 | [safe] 2 | directory = /usr/local/src/.terraform/modules/database 3 | directory = /usr/local/src/.terraform/modules/vpc 4 | 5 | 6 | -------------------------------------------------------------------------------- /deployment/terraform/config.tf: -------------------------------------------------------------------------------- 1 | provider "aws" { 2 | region = var.aws_region 3 | } 4 | 5 | terraform { 6 | backend "s3" { 7 | region = "us-east-1" 8 | encrypt = "true" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /deployment/terraform/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | terraform: 3 | image: quay.io/azavea/terraform:1.1.9 4 | volumes: 5 | - ./:/usr/local/src 6 | - $HOME/.aws:/root/.aws:ro 7 | - ${PWD}/.gitconfig:/root/.gitconfig 8 | environment: 9 | - PFB_DEBUG=1 10 | - AWS_PROFILE 11 | - GIT_COMMIT 12 | - PFB_SETTINGS_BUCKET 13 | - BATCH_ANALYSIS_JOB_NAME_REVISION 14 | - AWS_ACCESS_KEY_ID 15 | - AWS_SECRET_ACCESS_KEY 16 | working_dir: /usr/local/src 17 | entrypoint: terraform 18 | -------------------------------------------------------------------------------- /deployment/terraform/network.tf: -------------------------------------------------------------------------------- 1 | # VPC module for setting up vpc 2 | module "vpc" { 3 | source = "github.com/azavea/terraform-aws-vpc?ref=6.0.1" 4 | name = "pfb${var.environment}" 5 | region = var.aws_region 6 | key_name = var.aws_key_name 7 | cidr_block = var.vpc_cidr_block 8 | private_subnet_cidr_blocks = var.vpc_private_subnet_cidr_blocks 9 | public_subnet_cidr_blocks = var.vpc_public_subnet_cidr_blocks 10 | availability_zones = var.vpc_availibility_zones 11 | bastion_ami = var.bastion_ami 12 | bastion_instance_type = var.vpc_bastion_instance_type 13 | 14 | project = var.project 15 | environment = var.environment 16 | } 17 | 18 | -------------------------------------------------------------------------------- /deployment/terraform/task-definitions/nginx.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "cpu": 10, 4 | "essential": true, 5 | "image": "${app_server_nginx_url}", 6 | "extraHosts": [ 7 | { 8 | "hostname": "django", 9 | "ipAddress": "127.0.0.1" 10 | } 11 | ], 12 | "memory": 256, 13 | "name": "nginx", 14 | "portMappings": [ 15 | { 16 | "containerPort": 80, 17 | "hostPort": 0 18 | } 19 | ], 20 | "logConfiguration": { 21 | "logDriver": "syslog", 22 | "options": { 23 | "syslog-address": "${pfb_app_papertrail_endpoint}", 24 | "syslog-tls-ca-cert": "/etc/papertrail-bundle.pem", 25 | "tag": "nginx-redirect" 26 | } 27 | } 28 | } 29 | ] 30 | -------------------------------------------------------------------------------- /deployment/terraform/versions.tf: -------------------------------------------------------------------------------- 1 | 2 | terraform { 3 | required_version = ">= 0.13" 4 | required_providers { 5 | aws = { 6 | source = "hashicorp/aws" 7 | version = "~> 4.20.1" 8 | } 9 | template = { 10 | source = "hashicorp/template" 11 | version = "~> 2.1.0" 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /docker-compose.test.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: pfb-nginx:${GIT_COMMIT} 4 | build: 5 | context: ./src/nginx 6 | dockerfile: Dockerfile 7 | 8 | django: 9 | image: pfb-app:${GIT_COMMIT} 10 | build: 11 | context: ./src/django 12 | dockerfile: Dockerfile 13 | 14 | analysis: 15 | image: pfb-analysis:${GIT_COMMIT} 16 | build: 17 | context: ./src 18 | dockerfile: analysis/Dockerfile 19 | 20 | tilegarden: 21 | env_file: ./src/tilegarden/.env 22 | volumes: 23 | - ./src/tilegarden/claudia:/opt/pfb/tilegarden/claudia 24 | -------------------------------------------------------------------------------- /docs/architecture/adr-0000-architecture-documentation.md: -------------------------------------------------------------------------------- 1 | # 0000 - Architecture Documentation 2 | 3 | ## Context 4 | We need a way to document major architecture decisions; in the past we have used the [Architecture Decision Record (ADR) format](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions). On past projects, we have found the ADR format to be a useful way to write and manage architecture decisions. 5 | 6 | We have written ADRs using both reStructuredText and Markdown formats on past projects. Certain documentation generators, such as Sphinx, can only use one of RST / Markdown. It is currently unknown which documentation generators we are likely to use for this project. The team is somewhat more comfortable writing in Markdown than RST. 7 | 8 | ## Decision 9 | We will continue to use the ADR format for writing architecture decisions for this project. We will use Markdown for formatting ADR documents. 10 | 11 | ## Consequences 12 | Major architectural decisions will need to be documented; changes to architectural decisions made via ADR will need to be documented in a superseding ADR. 13 | 14 | If we choose to use a documentation generator that does not support Markdown, we may need to convert existing ADRs to that tool's preferred format. 15 | -------------------------------------------------------------------------------- /prototype/Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = function(grunt) { 2 | 3 | // measures the time each task takes 4 | require('time-grunt')(grunt); 5 | 6 | // load grunt config 7 | require('load-grunt-config')(grunt); 8 | 9 | }; -------------------------------------------------------------------------------- /prototype/app/assets/css/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/css/.gitkeep -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello-old/font/mantle.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello-old/font/mantle.eot -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello-old/font/mantle.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello-old/font/mantle.ttf -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello-old/font/mantle.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello-old/font/mantle.woff -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello-old/font/mantle.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello-old/font/mantle.woff2 -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Font Awesome 5 | 6 | Copyright (C) 2016 by Dave Gandy 7 | 8 | Author: Dave Gandy 9 | License: SIL () 10 | Homepage: http://fortawesome.github.com/Font-Awesome/ 11 | 12 | 13 | ## Entypo 14 | 15 | Copyright (C) 2012 by Daniel Bruce 16 | 17 | Author: Daniel Bruce 18 | License: SIL (http://scripts.sil.org/OFL) 19 | Homepage: http://www.entypo.com 20 | 21 | 22 | ## Iconic 23 | 24 | Copyright (C) 2012 by P.J. Onori 25 | 26 | Author: P.J. Onori 27 | License: SIL (http://scripts.sil.org/OFL) 28 | Homepage: http://somerandomdude.com/work/iconic/ 29 | 30 | 31 | -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello/font/fontello.eot -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello/font/fontello.woff -------------------------------------------------------------------------------- /prototype/app/assets/fonts/fontello/font/fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/fonts/fontello/font/fontello.woff2 -------------------------------------------------------------------------------- /prototype/app/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/.gitkeep -------------------------------------------------------------------------------- /prototype/app/assets/images/footer-stripes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/footer-stripes.png -------------------------------------------------------------------------------- /prototype/app/assets/images/home-header-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/home-header-2.jpg -------------------------------------------------------------------------------- /prototype/app/assets/images/home-header-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/home-header-3.jpg -------------------------------------------------------------------------------- /prototype/app/assets/images/home-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/home-header.jpg -------------------------------------------------------------------------------- /prototype/app/assets/images/placesforbikes-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/placesforbikes-icon.png -------------------------------------------------------------------------------- /prototype/app/assets/images/placesforbikes-logo-vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/placesforbikes-logo-vertical.png -------------------------------------------------------------------------------- /prototype/app/assets/images/placesforbikes-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/placesforbikes-logo-white.png -------------------------------------------------------------------------------- /prototype/app/assets/images/placesforbikes-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/images/placesforbikes-logo.png -------------------------------------------------------------------------------- /prototype/app/assets/js/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/prototype/app/assets/js/.gitkeep -------------------------------------------------------------------------------- /prototype/app/assets/js/main.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | 3 | /* Scroll title fix in sidebar */ 4 | var container = $('#scrollHeaders'), 5 | section = $('#scrollHeaders section'); 6 | 7 | $(container).on('scroll', function() { 8 | containerTop = $(this).offset().top; 9 | 10 | $(section).each(function() { 11 | var topDistance = $(this).offset().top; 12 | var topDistance2 = $(this).scrollTop(); 13 | 14 | if ( (topDistance) <= containerTop ) { 15 | $(this).addClass('active'); 16 | $(this).children('.section-title').css('top', containerTop); 17 | } else { 18 | $(this).removeClass('active'); 19 | $(this).children('.section-title').css('top', 'initial'); 20 | } 21 | }); 22 | }); 23 | }) -------------------------------------------------------------------------------- /prototype/app/assets/sass/base/_animation.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | opacity: 0; 3 | transition: .15s linear opacity; 4 | &.in { 5 | opacity: 1; 6 | } 7 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/base/_base.scss: -------------------------------------------------------------------------------- 1 | *, *:after, *:before { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | font-size: 62.5%; 7 | font-family: 'Cabin Condensed', sans-serif; 8 | height: 100%; 9 | } 10 | 11 | body { 12 | font-size: 14px; 13 | font-size: 1.4rem; 14 | color: $text-base; 15 | height: 100%; 16 | } 17 | 18 | a { 19 | text-decoration: none; 20 | font-weight: 600; 21 | color: $brand-primary; 22 | 23 | &:hover { 24 | color: darken($brand-primary, 10%); 25 | cursor: pointer; 26 | } 27 | 28 | &:disabled, &.disabled, &[disabled] { 29 | opacity: .5; 30 | cursor: not-allowed; 31 | 32 | &:hover { 33 | color: darken($brand-primary, 10%); 34 | } 35 | } 36 | } 37 | 38 | hr { 39 | margin-top: 1rem; 40 | margin-bottom: 1rem; 41 | border: 0; 42 | border-top: 1px solid #797979; 43 | clear: both; 44 | } 45 | -------------------------------------------------------------------------------- /prototype/app/assets/sass/base/_list.scss: -------------------------------------------------------------------------------- 1 | dl { 2 | @extend %clearfix; 3 | } 4 | 5 | dl > dt { 6 | font-weight: 600; 7 | clear: both; 8 | margin: 5px 0; 9 | } 10 | 11 | dl > dd { 12 | margin-bottom: 10px; 13 | margin-left: 0; 14 | } 15 | 16 | ul { 17 | li { 18 | margin-top: 1rem; 19 | margin-bottom: 1rem; 20 | } 21 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/base/_typography.scss: -------------------------------------------------------------------------------- 1 | @mixin heading-tags { 2 | h1, h2, h3, h4, h5, h6, 3 | .h1, .h2, .h3, .h4, .h5, .h6 { 4 | @content; 5 | } 6 | } 7 | 8 | @include heading-tags { 9 | margin-bottom: 1rem; 10 | line-height: 1.2; 11 | color: #000; 12 | font-weight: bold; 13 | letter-spacing: 1px; 14 | } 15 | 16 | h1, h2, h3, 17 | .h1, .h2, .h3 { 18 | margin-top: 2rem; 19 | margin-bottom: 3.5rem; 20 | } 21 | 22 | h4, h5, h6, 23 | .h4, .h5, .h6 { 24 | margin-top: 1.3rem; 25 | } 26 | 27 | h1, .h1 { 28 | font-size: 4rem; 29 | } 30 | 31 | h2, .h2 { 32 | font-size: 2.8rem; 33 | } 34 | 35 | h3, .h3 { 36 | font-size: 2.2rem; 37 | } 38 | 39 | h4, .h4 { 40 | font-size: 1.8rem; 41 | } 42 | 43 | h5, .h5 { 44 | font-size: 1.6rem; 45 | } 46 | 47 | h6, .h6 { 48 | font-size: 1.6rem; 49 | } 50 | 51 | p, .p { 52 | font-size: 1.6rem; 53 | font-weight: 400; 54 | line-height: 1.6; 55 | margin-top: 1rem; 56 | margin-bottom: 2.5rem; 57 | } 58 | 59 | .font-size-small { 60 | font-size: 1.4rem; 61 | } 62 | 63 | .text-uppercase { 64 | text-transform: uppercase; 65 | } 66 | 67 | .text-capitalize { 68 | text-transform: capitalize; 69 | } 70 | 71 | .text-lowercase { 72 | text-transform: lowercase; 73 | } 74 | 75 | .text-center { 76 | text-align: center; 77 | } 78 | 79 | .text-right { 80 | text-align: right; 81 | } 82 | 83 | .text-left { 84 | text-align: left; 85 | } 86 | -------------------------------------------------------------------------------- /prototype/app/assets/sass/components/_image.scss: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | } 4 | 5 | .center-img { 6 | display: block; 7 | margin: auto; 8 | max-width: 100%; 9 | height: auto; 10 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/components/_map.scss: -------------------------------------------------------------------------------- 1 | .map { 2 | position: absolute; 3 | top: 0; 4 | left: 0; 5 | right: 0; 6 | bottom: 0; 7 | background-color: #eee; 8 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/components/_metrics.scss: -------------------------------------------------------------------------------- 1 | .metric-list { 2 | margin: 0; 3 | list-style-type: none; 4 | padding: 0; 5 | position: relative; 6 | z-index: 10; 7 | 8 | li { 9 | padding: 2rem; 10 | margin: 0; 11 | border-top: 1px solid #ddd; 12 | font-size: 1.8rem; 13 | } 14 | 15 | .network-score { 16 | float: right; 17 | margin-top: -5px !important; 18 | } 19 | 20 | .list-button { 21 | text-align: center; 22 | } 23 | } 24 | 25 | .metric-details { 26 | flex: 1; 27 | padding: 2rem; 28 | 29 | h1, h2, h3, h4 { 30 | margin: 0; 31 | } 32 | 33 | p:last-of-type { 34 | margin-bottom: 0; 35 | } 36 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/components/_nav.scss: -------------------------------------------------------------------------------- 1 | nav { 2 | 3 | a { 4 | display: inline-block; 5 | padding: 10px; 6 | } 7 | } 8 | 9 | nav.social { 10 | font-size: 1.8rem; 11 | 12 | a:not(:hover) { 13 | color: #fff; 14 | } 15 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/components/_network-score.scss: -------------------------------------------------------------------------------- 1 | .network-score { 2 | margin-top: 1rem; 3 | margin-bottom: 1rem; 4 | font-size: 4rem; 5 | font-weight: bold; 6 | 7 | &.large { 8 | font-size: 7rem; 9 | margin-bottom: 0; 10 | } 11 | 12 | &.small { 13 | font-size: 2.5rem; 14 | margin: 0; 15 | } 16 | 17 | .h3, h3 { 18 | margin: 0; 19 | } 20 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/components/_tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | position: relative; 3 | z-index: 999; 4 | color: $brand-primary; 5 | white-space: nowrap; 6 | font-size: 1.4rem; 7 | height: 20px; 8 | display: inline-block; 9 | 10 | &:after { 11 | content: attr(title); 12 | position: absolute; 13 | top: 50%; 14 | left: 100%; 15 | transform: translate(0%, -50%); 16 | background: black; 17 | border-radius: 2px; 18 | color: white; 19 | font-style: normal; 20 | text-align: left; 21 | opacity: 0; 22 | pointer-events: none; 23 | font-size: 1.4rem; 24 | padding: 4px 10px; 25 | font-weight: 400; 26 | width: auto; 27 | min-width: 140px; 28 | max-width: 350px; 29 | white-space: normal; 30 | transition: opacity .2s ease-in-out; 31 | } 32 | 33 | &:before { 34 | content: ''; 35 | display: block; 36 | position: absolute; 37 | opacity: 0; 38 | top: 50%; 39 | left: 100%; 40 | transform: translate(-100%,-50%); 41 | border: 7px solid transparent; 42 | border-right-color: black; 43 | transition: opacity .2s ease-in-out; 44 | } 45 | 46 | &:hover:after, 47 | &:hover:before { 48 | opacity: 1; 49 | } 50 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/layout/_container.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | max-width: 970px; 4 | margin: auto; 5 | 6 | @include respond-to('sm-down') { 7 | padding: 0 1rem; 8 | } 9 | } 10 | 11 | .container-small { 12 | width: 100%; 13 | max-width: 960px; 14 | margin: auto; 15 | 16 | @include respond-to('sm-down') { 17 | padding: 1rem; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /prototype/app/assets/sass/layout/_footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | @extend %clearfix; 3 | display: block; 4 | background-color: #000; 5 | padding-top: 4rem; 6 | padding-bottom: 4rem; 7 | color: #fff; 8 | position: relative; 9 | border-top: 1px solid #333; 10 | 11 | .brand { 12 | max-width: 20rem; 13 | } 14 | 15 | .container { 16 | display: flex; 17 | align-items: baseline; 18 | 19 | @include respond-to(xs) { 20 | flex-direction: column; 21 | } 22 | } 23 | } 24 | 25 | .footer-left, 26 | .footer-right { 27 | flex: 1; 28 | } 29 | 30 | .footer-right { 31 | text-align: right; 32 | 33 | @include respond-to(xs) { 34 | text-align: left; 35 | } 36 | } 37 | 38 | .stripe { 39 | background-image: url(../images/footer-stripes.png); 40 | background-repeat: no-repeat; 41 | background-position: right; 42 | width: 100%; 43 | height: 10px; 44 | position: absolute; 45 | top: 1rem; 46 | right: 1rem; 47 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/layout/_header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | padding: 5rem 0; 3 | position: relative; 4 | z-index: 1; 5 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/layout/_navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | @extend %clearfix; 3 | position: relative; 4 | padding: 1rem; 5 | background-color: #000; 6 | z-index: 11; 7 | 8 | .container { 9 | max-width: 100%; 10 | display: flex; 11 | 12 | @include respond-to('sm-down') { 13 | flex-direction: column; 14 | text-align: center; 15 | } 16 | } 17 | 18 | nav { 19 | display: inline-block; 20 | vertical-align: middle; 21 | 22 | a { 23 | padding: 18px 20px; 24 | color: #fff; 25 | text-transform: uppercase; 26 | 27 | &:hover { 28 | color: $link-color; 29 | } 30 | } 31 | } 32 | } 33 | 34 | .navbar-left { 35 | flex: 1; 36 | } 37 | 38 | .navbar-right { 39 | 40 | @include respond-to('sm-down') { 41 | justify-content: flex-end; 42 | } 43 | } 44 | 45 | .brand { 46 | display: inline-block; 47 | vertical-align: middle; 48 | 49 | > img { 50 | max-height: 5rem; 51 | top: 4px; 52 | position: relative; 53 | } 54 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/layout/_section.scss: -------------------------------------------------------------------------------- 1 | section { 2 | @extend %clearfix; 3 | display: block; 4 | position: relative; 5 | padding-top: 6rem; 6 | padding-bottom: 6rem; 7 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/main.scss: -------------------------------------------------------------------------------- 1 | /** 2 | * Utils 3 | **/ 4 | @import 5 | "utils/mixins", 6 | "utils/variables"; 7 | 8 | /** 9 | * Base 10 | **/ 11 | @import 12 | "base/normalize", 13 | "base/animation", 14 | "base/base", 15 | "base/typography", 16 | "base/list", 17 | "base/grid", 18 | "base/helpers"; 19 | 20 | /** 21 | * Layout 22 | **/ 23 | @import 24 | "layout/container", 25 | "layout/header", 26 | "layout/footer", 27 | "layout/navbar", 28 | "layout/section"; 29 | 30 | /** 31 | * Components 32 | **/ 33 | @import 34 | "components/button", 35 | "components/card", 36 | "components/dropdown", 37 | "components/image", 38 | "components/form", 39 | "components/metrics", 40 | "components/nav", 41 | "components/network-score", 42 | "components/map", 43 | "components/tooltip"; 44 | 45 | /** 46 | * Pages 47 | **/ 48 | @import 49 | "pages/compare", 50 | "pages/location", 51 | "pages/home", 52 | "pages/mapping"; -------------------------------------------------------------------------------- /prototype/app/assets/sass/pages/_compare.scss: -------------------------------------------------------------------------------- 1 | .comparisons { 2 | background-color: $shade-light; 3 | 4 | .card { 5 | margin-top: 2rem; 6 | } 7 | 8 | .compare-remove { 9 | position: absolute; 10 | right: 10px; 11 | top: 10px; 12 | font-size: 22px; 13 | color: #000; 14 | opacity: .5; 15 | transition: .2s ease-in-out opacity; 16 | 17 | &:hover { 18 | opacity: 1; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/pages/_location.scss: -------------------------------------------------------------------------------- 1 | .location-overview { 2 | 3 | .metric-details { 4 | padding: 1rem 2rem; 5 | background-color: $shade-light; 6 | border-bottom: 1px solid darken($shade-light, 5%); 7 | position: relative; 8 | z-index: 1; 9 | 10 | .column { 11 | padding: 0; 12 | } 13 | } 14 | } -------------------------------------------------------------------------------- /prototype/app/assets/sass/utils/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* * * * 2 | * Responsive Breakpoint Manager 3 | * requires $breakpoints from _variables.scss > $breakpoints 4 | * Useage: @include respond-to('small') {...} 5 | * * * */ 6 | @mixin respond-to($breakpoint) { 7 | // If the key exists in the map 8 | @if map-has-key($breakpoints, $breakpoint) { 9 | // Prints a media query based on the value 10 | @media #{inspect(map-get($breakpoints, $breakpoint))} { 11 | @content; 12 | } 13 | } 14 | 15 | // If the key doesn't exist in the map 16 | @else { 17 | @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " 18 | + "Available breakpoints are: #{map-keys($breakpoints)}."; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /prototype/app/assets/sass/utils/_variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * LMC Variables 3 | * If overridng bootstrap please place inside _bs-variables.scss 4 | */ 5 | 6 | // Brand Colors: 7 | $brand-primary: #00a1e1; // blue 8 | $brand-secondary: #e31d1a; // red 9 | 10 | // Base Colors: 11 | $text-base: #000000; 12 | $shade-light: #f3f3f3; 13 | $shade-normal: #bcbcbc; 14 | $shade-dark: #9da0a7; 15 | $link-color: $brand-primary; 16 | $white: #fff; 17 | 18 | // Action Colors: 19 | $warning: #E69348; // orange 20 | $danger: #F98094; // red 21 | $success: #21ce90; // red 22 | 23 | // Border Radius 24 | $border-radius-base: 2px; 25 | 26 | 27 | /* * * * 28 | * Screen Sizes 29 | * Used in _mixins.scss > @mixin respond-to($breakpoint) 30 | * * * */ 31 | $breakpoints: ( 32 | 33 | 'xxs': (max-width: 480px), 34 | 35 | 'xs': (max-width: 767px), 36 | 37 | 'sm': "(min-width: 768px) and (max-width: 991px)", 38 | 'sm-up': (min-width: 768px), 39 | 'sm-down': (max-width: 991px), 40 | 41 | 'md': "(min-width: 992px) and (max-width: 1180px)", 42 | 'md-up': (min-width: 992px), 43 | 'md-down': (max-width: 1180px), 44 | 45 | 'lg': (min-width: 1181px), 46 | ); 47 | 48 | 49 | /* * * * 50 | * Columns 51 | * * * */ 52 | $column-count: 12; 53 | $column-padding: 1rem; 54 | -------------------------------------------------------------------------------- /prototype/grunt/aliases.yaml: -------------------------------------------------------------------------------- 1 | default: 2 | - 'sass' 3 | - 'postcss' 4 | - 'browserSync' 5 | - 'watch' -------------------------------------------------------------------------------- /prototype/grunt/browserSync.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | dev: { 3 | bsFiles: { 4 | src : [ 5 | 'app/**/*.css', 6 | 'app/**/*.html' 7 | ] 8 | }, 9 | options: { 10 | watchTask: true, 11 | server: './app' 12 | } 13 | } 14 | }; -------------------------------------------------------------------------------- /prototype/grunt/concurrent.js: -------------------------------------------------------------------------------- 1 | // This file goes with grunt-concurrent. Not in use 2 | module.exports = { 3 | first: [ 4 | 'sass', 5 | 'postcss' 6 | ], 7 | second: [ 8 | 'watch', 9 | 'browserSync' 10 | ] 11 | }; -------------------------------------------------------------------------------- /prototype/grunt/postcss.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | map: false, 4 | processors: [ 5 | require('autoprefixer')({browsers: ['last 2 versions']}), 6 | require('cssnano')({ 7 | 'safe': true 8 | }) // minify the result 9 | ], 10 | }, 11 | dist: { 12 | src: 'app/assets/css/*.css' 13 | } 14 | }; 15 | -------------------------------------------------------------------------------- /prototype/grunt/sass.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | options: { 3 | sourceMap: true 4 | }, 5 | dev: { 6 | files: { 7 | 'app/assets/css/main.css': 'app/assets/sass/main.scss' 8 | } 9 | } 10 | }; -------------------------------------------------------------------------------- /prototype/grunt/watch.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | script: { 3 | files: ['app/**/*.js'], 4 | options: { 5 | spawn: false, 6 | } 7 | }, 8 | 9 | css: { 10 | files: ['app/**/*.scss'], 11 | tasks: ['sass', 'postcss'], 12 | options: { 13 | spawn: false, 14 | } 15 | } 16 | }; -------------------------------------------------------------------------------- /prototype/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mantle", 3 | "version": "0.1.0", 4 | "description": "Azavea's front end framework for GIS applications", 5 | "devDependencies": { 6 | "autoprefixer": "^6.3.6", 7 | "cssnano": "^3.5.2", 8 | "grunt": "^0.4.5", 9 | "grunt-browser-sync": "^2.2.0", 10 | "grunt-contrib-watch": "^1.0.0", 11 | "grunt-newer": "^1.2.0", 12 | "grunt-postcss": "^0.8.0", 13 | "grunt-sass": "^1.1.0", 14 | "load-grunt-config": "^0.19.1", 15 | "load-grunt-tasks": "^3.5.0", 16 | "time-grunt": "^1.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /scripts/clean-analysis-volumes: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd $(dirname "${0}") 6 | 7 | if [[ -n "${PFB_DEBUG}" ]]; then 8 | set -x 9 | fi 10 | 11 | function usage() { 12 | echo -n \ 13 | "Usage: $(basename "$0") 14 | 15 | Removes all 'pfb-analysis' docker containers and their volumes to free up storage space, 16 | as well as any dangling docker volumes. 17 | 18 | " 19 | } 20 | 21 | if [ "${BASH_SOURCE[0]}" = "${0}" ] 22 | then 23 | if [ "${1:-}" = "--help" ] 24 | then 25 | usage 26 | else 27 | FILTERS='--filter "label=type=pfb-analysis" --filter "status=exited"' 28 | 29 | echo "The following containers and their volumes will be deleted:" 30 | eval docker ps -a $FILTERS 31 | echo "The following dangling docker volumes will be deleted:" 32 | docker volume ls -f 'dangling=true' 33 | 34 | read -r -p "Are you sure? [y/N] " response 35 | response=${response,,} # tolower 36 | if [[ "$response" =~ ^(yes|y)$ ]] 37 | then 38 | docker rm -v $(eval docker ps -aq $FILTERS) 39 | docker volume rm $(docker volume ls -f 'dangling=true' -q) 40 | fi 41 | fi 42 | fi 43 | -------------------------------------------------------------------------------- /scripts/console: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd `dirname "${0}"` 6 | 7 | if [[ -n "${PFD_DEBUG}" ]]; then 8 | set -x 9 | fi 10 | 11 | usage() { 12 | echo -n "$(basename "${0}") [OPTION] 13 | Login to a running Docker container\'s shell. 14 | Options: 15 | database Database container 16 | django Django container 17 | django-q Django Q container 18 | angularjs AngularJS container 19 | nginx Nginx container 20 | tilegarden Tilegarden container 21 | help Display this help text 22 | " 23 | } 24 | 25 | case $1 in 26 | django|django-q|nginx|angularjs|tilegarden) NORMAL_CONTAINER=1 ;; 27 | database) DATABASE_CONTAINER=1 ;; 28 | help|*) usage; exit 1 ;; 29 | esac 30 | 31 | pushd .. 32 | 33 | if [ -n "$NORMAL_CONTAINER" ]; then 34 | docker compose exec "${1}" /bin/bash 35 | fi 36 | 37 | if [ -n "$DATABASE_CONTAINER" ]; then 38 | docker compose exec database gosu postgres psql -d pfb 39 | fi 40 | 41 | popd 42 | -------------------------------------------------------------------------------- /scripts/django-manage: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd `dirname "${0}"` 6 | 7 | if [[ -n "${PFB_DEBUG}" ]]; then 8 | set -x 9 | fi 10 | 11 | function usage() { 12 | echo -n \ 13 | "Usage: $(basename "$0") 14 | 15 | Run a Django management command in the running django container 16 | 17 | " 18 | } 19 | 20 | if [ "${BASH_SOURCE[0]}" = "${0}" ] 21 | then 22 | if [ "${1:-}" = "--help" ] 23 | then 24 | usage 25 | else 26 | pushd .. 27 | 28 | docker compose exec django python3 manage.py "${@}" 29 | 30 | popd 31 | fi 32 | fi 33 | -------------------------------------------------------------------------------- /scripts/server: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd `dirname "${0}"` 6 | 7 | if [[ -n "${PFB_DEBUG}" ]]; then 8 | set -x 9 | fi 10 | 11 | function usage() { 12 | echo -n \ 13 | "Usage: $(basename "$0") 14 | 15 | Starts servers using docker-compose. 16 | 17 | " 18 | } 19 | 20 | if [ "${BASH_SOURCE[0]}" = "${0}" ] 21 | then 22 | if [ "${1:-}" = "--help" ] 23 | then 24 | usage 25 | else 26 | pushd .. 27 | 28 | docker compose up --build nginx django django-q angularjs tilegarden 29 | 30 | popd 31 | fi 32 | fi 33 | -------------------------------------------------------------------------------- /scripts/setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd `dirname "${0}"` 6 | 7 | if [[ -n "${PFB_DEBUG}" ]]; then 8 | set -x 9 | fi 10 | 11 | function usage() { 12 | echo -n \ 13 | "Usage: $(basename "$0") 14 | 15 | Sets up the development environment by building containers and loading example data. 16 | 17 | " 18 | } 19 | 20 | if [ "${BASH_SOURCE[0]}" = "${0}" ] 21 | then 22 | if [ "${1:-}" = "--help" ] 23 | then 24 | usage 25 | else 26 | ./update --load-data 27 | fi 28 | fi 29 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | DIR="$(dirname "$0")" 6 | 7 | function usage() { 8 | echo -n \ 9 | "Usage: $(basename "$0") 10 | Run application tests 11 | " 12 | } 13 | if [ "${BASH_SOURCE[0]}" = "${0}" ]; then 14 | if [ "${1:-}" = "--help" ]; then 15 | usage 16 | else 17 | echo "running python tests..." 18 | docker compose -f "${DIR}/../docker-compose.yml" run \ 19 | --rm --entrypoint "python3 manage.py test --noinput" django 20 | 21 | echo "running Tilegarden tests..." 22 | docker compose run --rm --no-deps tilegarden yarn test 23 | 24 | echo "running angularjs linter..." 25 | docker compose run --rm --no-deps angularjs gulp lint 26 | 27 | echo "running angularjs build..." 28 | docker compose run --rm --no-deps angularjs gulp build 29 | 30 | echo "tests finished" 31 | fi 32 | fi 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/analysis/.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | *.log 3 | -------------------------------------------------------------------------------- /src/analysis/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2017 Azavea, Inc. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); 4 | you may not use this file except in compliance with the License. 5 | You may obtain a copy of the License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software 10 | distributed under the License is distributed on an "AS IS" BASIS, 11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | See the License for the specific language governing permissions and 13 | limitations under the License. 14 | -------------------------------------------------------------------------------- /src/analysis/README.md: -------------------------------------------------------------------------------- 1 | # pfb 2 | 3 | ## Docker 4 | 5 | To run the analysis in docker, first build the docker image. 6 | 7 | From the `src` folder, run 8 | ```bash 9 | docker build -t pfb -f analysis/Dockerfile . 10 | ``` 11 | 12 | Then run the analysis as follows: 13 | 14 | ```bash 15 | docker run \ 16 | -e PFB_SHPFILE=/data/neighborhood_boundary.shp \ 17 | -e PFB_STATE=ma \ 18 | -e PFB_STATE_FIPS=25 \ 19 | -e NB_INPUT_SRID=2249 \ 20 | -e NB_BOUNDARY_BUFFER=11000 \ 21 | -v /vagrant/data/:/data/ \ 22 | pfb 23 | ``` 24 | 25 | The `-e` in this example sets environment variables, which will depend on the 26 | analysis you are running. 27 | 28 | The `-v` in this example mounts a local directory inside the docker container 29 | under `/data/`. The `PFB_SHPFILE` environment variable specifies the `.shp` 30 | file under this directory to use. 31 | 32 | This process of mounting a data directory with the input shapefile will be 33 | removed in favor of an import process. 34 | -------------------------------------------------------------------------------- /src/analysis/connectivity/reachable_roads_high_stress_calc.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- :nb_max_trip_distance psql var must be set before running this script, 5 | -- e.g. psql -v nb_max_trip_distance=2680 -f reachable_roads_high_stress_calc.sql 6 | ---------------------------------------- 7 | INSERT INTO generated.neighborhood_reachable_roads_high_stress ( 8 | base_road, 9 | target_road, 10 | total_cost 11 | ) 12 | SELECT r1.road_id, 13 | v2.road_id, 14 | sheds.agg_cost 15 | FROM neighborhood_ways r1, 16 | neighborhood_ways_net_vert v1, 17 | neighborhood_ways_net_vert v2, 18 | pgr_drivingDistance(' 19 | SELECT link_id AS id, 20 | source_vert AS source, 21 | target_vert AS target, 22 | link_cost AS cost 23 | FROM neighborhood_ways_net_link', 24 | v1.vert_id, 25 | :nb_max_trip_distance, 26 | directed := true 27 | ) sheds 28 | WHERE r1.road_id % :thread_num = :thread_no 29 | AND 30 | EXISTS ( 31 | SELECT 1 32 | FROM neighborhood_boundary AS b 33 | WHERE ST_Intersects(b.geom,r1.geom) 34 | ) 35 | AND r1.road_id = v1.road_id 36 | AND v2.vert_id = sheds.node; 37 | -------------------------------------------------------------------------------- /src/analysis/connectivity/reachable_roads_high_stress_cleanup.sql: -------------------------------------------------------------------------------- 1 | CREATE UNIQUE INDEX IF NOT EXISTS idx_neighborhood_rchblrdshistrss_b ON generated.neighborhood_reachable_roads_high_stress (base_road, target_road); 2 | CREATE INDEX IF NOT EXISTS idx_neighborhood_rchblrdshistrss_t ON generated.neighborhood_reachable_roads_high_stress (target_road); 3 | VACUUM ANALYZE generated.neighborhood_reachable_roads_high_stress; 4 | -------------------------------------------------------------------------------- /src/analysis/connectivity/reachable_roads_high_stress_prep.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | DROP TABLE IF EXISTS generated.neighborhood_reachable_roads_high_stress; 6 | 7 | CREATE TABLE generated.neighborhood_reachable_roads_high_stress ( 8 | id SERIAL PRIMARY KEY, 9 | base_road INT, 10 | target_road INT, 11 | total_cost FLOAT 12 | ); 13 | -------------------------------------------------------------------------------- /src/analysis/connectivity/reachable_roads_low_stress_calc.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- :nb_max_trip_distance psql var must be set before running this script, 5 | -- e.g. psql -v nb_max_trip_distance=2680 -f reachable_roads_low_stress_calc.sql 6 | ---------------------------------------- 7 | INSERT INTO generated.neighborhood_reachable_roads_low_stress ( 8 | base_road, 9 | target_road, 10 | total_cost 11 | ) 12 | SELECT r1.road_id, 13 | v2.road_id, 14 | sheds.agg_cost 15 | FROM neighborhood_ways r1, 16 | neighborhood_ways_net_vert v1, 17 | neighborhood_ways_net_vert v2, 18 | pgr_drivingDistance(' 19 | SELECT link_id AS id, 20 | source_vert AS source, 21 | target_vert AS target, 22 | link_cost AS cost 23 | FROM neighborhood_ways_net_link 24 | WHERE link_stress = 1', 25 | v1.vert_id, 26 | :nb_max_trip_distance, 27 | directed := true 28 | ) sheds 29 | WHERE r1.road_id % :thread_num = :thread_no 30 | AND 31 | EXISTS ( 32 | SELECT 1 33 | FROM neighborhood_boundary AS b 34 | WHERE ST_Intersects(b.geom,r1.geom) 35 | ) 36 | AND r1.road_id = v1.road_id 37 | AND v2.vert_id = sheds.node; 38 | -------------------------------------------------------------------------------- /src/analysis/connectivity/reachable_roads_low_stress_cleanup.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | CREATE INDEX IF NOT EXISTS idx_neighborhood_rchblrdslowstrss_b ON generated.neighborhood_reachable_roads_low_stress (base_road); 6 | CREATE INDEX IF NOT EXISTS idx_neighborhood_rchblrdslowstrss_t ON generated.neighborhood_reachable_roads_low_stress (target_road); 7 | VACUUM ANALYZE generated.neighborhood_reachable_roads_low_stress (base_road,target_road); 8 | -------------------------------------------------------------------------------- /src/analysis/connectivity/reachable_roads_low_stress_prep.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | DROP TABLE IF EXISTS generated.neighborhood_reachable_roads_low_stress; 6 | 7 | CREATE TABLE generated.neighborhood_reachable_roads_low_stress ( 8 | id SERIAL PRIMARY KEY, 9 | base_road INT, 10 | target_road INT, 11 | total_cost FLOAT 12 | ); 13 | -------------------------------------------------------------------------------- /src/analysis/features/class_adjustments.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- Adjusts functional class on residential 3 | -- and unclassified with bike facilities 4 | -- or multiple travel lanes to be 5 | -- tertiary 6 | ---------------------------------------- 7 | UPDATE received.neighborhood_ways 8 | SET functional_class = 'tertiary' 9 | WHERE functional_class IN ('residential','unclassified') 10 | AND ( 11 | ft_bike_infra IN ('track','buffered_lane','lane') 12 | OR tf_bike_infra IN ('track','buffered_lane','lane') 13 | OR ft_lanes > 1 14 | OR tf_lanes > 1 15 | OR speed_limit >= 30 16 | ); 17 | -------------------------------------------------------------------------------- /src/analysis/features/island.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- vars: 5 | -- :sigctl_search_dist=25 Search distance for traffic signals at adjacent intersection 6 | ---------------------------------------- 7 | UPDATE neighborhood_ways_intersections SET island = FALSE; 8 | 9 | UPDATE neighborhood_ways_intersections 10 | SET island = TRUE 11 | WHERE legs > 2 12 | AND EXISTS ( 13 | SELECT 1 14 | FROM neighborhood_osm_full_point osm 15 | WHERE osm.highway = 'crossing' 16 | AND osm."crossing:island" = 'yes' 17 | AND ST_DWithin(neighborhood_ways_intersections.geom, osm.way, :sigctl_search_dist) 18 | ); 19 | -------------------------------------------------------------------------------- /src/analysis/features/legs.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE received.neighborhood_ways_intersections 6 | SET legs = ( 7 | SELECT COUNT(road_id) 8 | FROM neighborhood_ways 9 | WHERE neighborhood_ways_intersections.int_id IN (intersection_from,intersection_to) 10 | ); 11 | -------------------------------------------------------------------------------- /src/analysis/features/one_way.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET one_way_car = NULL; 6 | 7 | -- ft direction 8 | UPDATE neighborhood_ways 9 | SET one_way_car = 'ft' 10 | FROM neighborhood_osm_full_line osm 11 | WHERE neighborhood_ways.osm_id = osm.osm_id 12 | AND trim(osm.oneway) IN ('1','yes'); 13 | 14 | -- tf direction 15 | UPDATE neighborhood_ways 16 | SET one_way_car = 'tf' 17 | FROM neighborhood_osm_full_line osm 18 | WHERE neighborhood_ways.osm_id = osm.osm_id 19 | AND trim(osm.oneway) = '-1'; 20 | -------------------------------------------------------------------------------- /src/analysis/features/rrfb.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- vars: 5 | -- :sigctl_search_dist=25 Search distance for traffic signals at adjacent intersection 6 | ---------------------------------------- 7 | UPDATE neighborhood_ways_intersections SET rrfb = FALSE; 8 | 9 | UPDATE neighborhood_ways_intersections 10 | SET rrfb = TRUE 11 | WHERE legs > 2 12 | AND EXISTS ( 13 | SELECT 1 14 | FROM neighborhood_osm_full_point osm 15 | WHERE osm.highway = 'crossing' 16 | AND osm.flashing_lights IN ('yes','button','always','sensor') 17 | AND ST_DWithin(neighborhood_ways_intersections.geom, osm.way, :sigctl_search_dist) 18 | ); 19 | -------------------------------------------------------------------------------- /src/analysis/features/speed_limit.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET speed_limit = NULL; 6 | 7 | -- convert kmph to mph and round to nearest 5 8 | UPDATE neighborhood_ways 9 | SET speed_limit = ROUND(substring(osm.maxspeed from '\d+')::INT / 1.609 / 5)*5 10 | FROM neighborhood_osm_full_line osm 11 | WHERE neighborhood_ways.osm_id = osm.osm_id 12 | AND (osm.maxspeed LIKE '% kmph' OR osm.maxspeed ~ '^\d+(\.\d+)?$'); 13 | 14 | UPDATE neighborhood_ways 15 | SET speed_limit = substring(osm.maxspeed from '\d+')::INT 16 | FROM neighborhood_osm_full_line osm 17 | WHERE neighborhood_ways.osm_id = osm.osm_id 18 | AND osm.maxspeed LIKE '% mph'; 19 | -------------------------------------------------------------------------------- /src/analysis/features/stops.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- vars: 5 | -- :sigctl_search_dist=25 Search distance for traffic signals at adjacent intersection 6 | ---------------------------------------- 7 | UPDATE neighborhood_ways_intersections SET stops = 'f'; 8 | 9 | UPDATE neighborhood_ways_intersections 10 | SET stops = 't' 11 | FROM neighborhood_osm_full_point osm 12 | WHERE neighborhood_ways_intersections.osm_id = osm.osm_id 13 | AND osm.highway = 'stop' 14 | AND osm.stop = 'all'; 15 | 16 | UPDATE neighborhood_ways_intersections 17 | SET stops = 't' 18 | WHERE legs > 2 19 | AND EXISTS ( 20 | SELECT 1 21 | FROM neighborhood_ways_intersections i 22 | WHERE i.stops 23 | AND ST_DWithin(neighborhood_ways_intersections.geom, i.geom, :sigctl_search_dist) 24 | ); 25 | -------------------------------------------------------------------------------- /src/analysis/features/streetlight/streetlight_destinations.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- Prepares a table to be exported to StreetLightData 5 | ---------------------------------------- 6 | DROP TABLE IF EXISTS neighborhood_streetlight_destinations; 7 | CREATE TABLE generated.neighborhood_streetlight_destinations ( 8 | id SERIAL PRIMARY KEY, 9 | geom geometry(multipolygon,4326), 10 | name TEXT, 11 | blockid10 TEXT, 12 | is_pass INT 13 | ); 14 | 15 | INSERT INTO neighborhood_streetlight_destinations ( 16 | blockid10, 17 | name, 18 | geom, 19 | is_pass 20 | ) 21 | SELECT blocks.blockid10, 22 | blocks.blockid10, 23 | -- Transform to 4326, this is what StreetLightData expects 24 | ST_Transform(blocks.geom,4326), 25 | 0 26 | FROM neighborhood_census_blocks blocks, 27 | neighborhood_boundary b 28 | WHERE ST_Intersects(blocks.geom,b.geom); 29 | -------------------------------------------------------------------------------- /src/analysis/features/width_ft.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET width_ft = NULL; 6 | 7 | -- feet 8 | UPDATE neighborhood_ways 9 | SET width_ft = substring(osm.width from '\d+\.?\d?\d?')::FLOAT 10 | FROM neighborhood_osm_full_line osm 11 | WHERE neighborhood_ways.osm_id = osm.osm_id 12 | AND osm.width IS NOT NULL 13 | AND osm.width LIKE '% ft'; 14 | 15 | -- meters 16 | UPDATE neighborhood_ways 17 | SET width_ft = 3.28084 * substring(osm.width from '\d+\.?\d?\d?')::FLOAT 18 | FROM neighborhood_osm_full_line osm 19 | WHERE neighborhood_ways.osm_id = osm.osm_id 20 | AND osm.width IS NOT NULL 21 | AND osm.width LIKE '% m'; 22 | 23 | -- no units (default=meters) 24 | -- N.B. we weed out anything more than 20, since that's likely either bogus 25 | -- or not in meters 26 | UPDATE neighborhood_ways 27 | SET width_ft = 3.28084 * substring(osm.width from '\d+\.?\d?\d?')::FLOAT 28 | FROM neighborhood_osm_full_line osm 29 | WHERE neighborhood_ways.osm_id = osm.osm_id 30 | AND osm.width IS NOT NULL 31 | AND substring(osm.width from '\d+\.?\d?\d?')::FLOAT < 20; 32 | -------------------------------------------------------------------------------- /src/analysis/import/clip_osm.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | -- :nb_boundary_buffer psql var must be set before running this script, 5 | -- e.g. psql -v nb_boundary_buffer=1000 -f clip_osm.sql 6 | ---------------------------------------- 7 | 8 | 9 | DELETE FROM neighborhood_ways AS ways 10 | USING neighborhood_boundary AS boundary 11 | WHERE NOT ST_DWithin(ways.geom, boundary.geom, :nb_boundary_buffer); 12 | 13 | DELETE FROM neighborhood_ways_intersections AS intersections 14 | USING neighborhood_boundary AS boundary 15 | WHERE NOT ST_DWithin(intersections.geom, boundary.geom, :nb_boundary_buffer); 16 | 17 | DELETE FROM neighborhood_osm_full_line AS lines 18 | USING neighborhood_boundary AS boundary 19 | WHERE NOT ST_DWithin(lines.way, boundary.geom, :nb_boundary_buffer); 20 | 21 | DELETE FROM neighborhood_osm_full_point AS points 22 | USING neighborhood_boundary AS boundary 23 | WHERE NOT ST_DWithin(points.way, boundary.geom, :nb_boundary_buffer); 24 | 25 | DELETE FROM neighborhood_osm_full_polygon AS polygons 26 | USING neighborhood_boundary AS boundary 27 | WHERE NOT ST_DWithin(polygons.way, boundary.geom, :nb_boundary_buffer); 28 | 29 | DELETE FROM neighborhood_osm_full_roads AS roads 30 | USING neighborhood_boundary AS boundary 31 | WHERE NOT ST_DWithin(roads.way, boundary.geom, :nb_boundary_buffer); 32 | -------------------------------------------------------------------------------- /src/analysis/import/mapconfig_cycleway.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/analysis/import/mapconfig_highway.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/analysis/scripts/import.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd `dirname "${0}"` 6 | 7 | if [[ -n "${PFB_DEBUG}" ]]; then 8 | set -x 9 | fi 10 | 11 | function usage() { 12 | echo -n \ 13 | " 14 | Usage: $(basename "$0") 15 | 16 | Import all necessary files to run the analysis. See the scripts this calls for specific 17 | ENV configuration options. 18 | 19 | " 20 | } 21 | 22 | if [ "${BASH_SOURCE[0]}" = "${0}" ] 23 | then 24 | if [ "${1:-}" = "--help" ] || [ -z "${1:-}" ] 25 | then 26 | usage 27 | else 28 | PFB_SHPFILE="${1}" 29 | PFB_OSM_FILE="${2}" 30 | PFB_COUNTRY="${3}" 31 | PFB_STATE="${4}" 32 | PFB_STATE_FIPS="${5}" 33 | 34 | ../import/import_neighborhood.sh $PFB_SHPFILE $PFB_COUNTRY $PFB_STATE $PFB_STATE_FIPS 35 | if [ "$RUN_IMPORT_JOBS" == "1" ]; then 36 | ../import/import_jobs.sh $PFB_COUNTRY $PFB_STATE 37 | else 38 | echo "Skipping Importing Jobs" 39 | fi 40 | ../import/import_osm.sh $PFB_OSM_FILE 41 | fi 42 | fi 43 | -------------------------------------------------------------------------------- /src/analysis/scripts/utils.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function import_geometries_for_job() { 4 | if [ -n "${PFB_JOB_ID}" ]; 5 | then 6 | /opt/pfb/django/manage.py import_results_shapefiles "${PFB_JOB_ID}" 7 | fi 8 | } 9 | 10 | function update_status() { 11 | # Usage: 12 | # update_status STATUS [step [message]] 13 | echo "Updating job status: $@" 14 | if [ -n "${PFB_JOB_ID}" ]; 15 | then 16 | /opt/pfb/django/manage.py update_status "${PFB_JOB_ID}" "$@" 17 | fi 18 | } 19 | 20 | function update_overall_scores() { 21 | # Usage: 22 | # update_overall_scores OVERALL_SCORES_CSV 23 | if [ -n "${PFB_JOB_ID}" ]; 24 | then 25 | /opt/pfb/django/manage.py load_overall_scores --skip-columns \ 26 | human_explanation \ 27 | "${PFB_JOB_ID}" "$@" 28 | fi 29 | } 30 | 31 | function update_residential_speed_limit() { 32 | # Usage: 33 | # update_residential_speed_limit RESIDENTIAL_SPEED_LIMIT_CSV 34 | if [ -n "${PFB_JOB_ID}" ]; 35 | then 36 | /opt/pfb/django/manage.py load_residential_speed_limit "${PFB_JOB_ID}" "$@" 37 | fi 38 | } 39 | 40 | function set_job_attr() { 41 | # Usage: 42 | # update_job_attr ATTRIBUTE VALUE 43 | if [ -n "${PFB_JOB_ID}" ]; 44 | then 45 | /opt/pfb/django/manage.py set_job_attr "${PFB_JOB_ID}" "$@" 46 | fi 47 | } 48 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_link_ints.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET ft_int_stress = 1, tf_int_stress = 1 6 | WHERE functional_class LIKE '%_link'; 7 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_living_street.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET ft_seg_stress = NULL, tf_seg_stress = NULL 6 | WHERE functional_class = 'living_street'; 7 | 8 | UPDATE neighborhood_ways 9 | SET ft_seg_stress = 3, 10 | tf_seg_stress = 3 11 | FROM neighborhood_osm_full_line osm 12 | WHERE functional_class = 'living_street' 13 | AND neighborhood_ways.osm_id = osm.osm_id 14 | AND osm.bicycle = 'no'; 15 | 16 | UPDATE neighborhood_ways 17 | SET ft_seg_stress = COALESCE(ft_seg_stress,1), 18 | tf_seg_stress = COALESCE(tf_seg_stress,1) 19 | WHERE functional_class = 'living_street'; 20 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_motorway-trunk.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET ft_seg_stress = NULL, tf_seg_stress = NULL 6 | WHERE functional_class IN ('motorway','motorway_link','trunk','trunk_link'); 7 | 8 | UPDATE neighborhood_ways SET ft_seg_stress = 3, tf_seg_stress = 3 9 | WHERE functional_class IN ('motorway','motorway_link','trunk','trunk_link'); 10 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_motorway-trunk_ints.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | -- assume low stress, since these juncions would always be controlled or free flowing 6 | UPDATE neighborhood_ways SET ft_int_stress = 1, tf_int_stress = 1 7 | WHERE functional_class IN ('motorway','trunk'); 8 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_one_way_reset.sql: -------------------------------------------------------------------------------- 1 | -- reset opposite stress for one-way 2 | UPDATE neighborhood_ways 3 | SET ft_seg_stress = NULL 4 | WHERE one_way = 'tf'; 5 | UPDATE neighborhood_ways 6 | SET tf_seg_stress = NULL 7 | WHERE one_way = 'ft'; 8 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_path.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET ft_seg_stress = NULL, tf_seg_stress = NULL 6 | WHERE functional_class = 'path'; 7 | 8 | UPDATE neighborhood_ways 9 | SET ft_seg_stress = 1, 10 | tf_seg_stress = 1 11 | WHERE functional_class = 'path'; 12 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_primary_ints.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | -- assume low stress, since these juncions would always be controlled or free flowing 6 | UPDATE neighborhood_ways SET ft_int_stress = 1, tf_int_stress = 1 7 | WHERE functional_class = 'primary'; 8 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_secondary_ints.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | -- assume low stress, since these juncions would always be controlled or free flowing 6 | UPDATE neighborhood_ways SET ft_int_stress = 1, tf_int_stress = 1 7 | WHERE functional_class = 'secondary'; 8 | -------------------------------------------------------------------------------- /src/analysis/stress/stress_track.sql: -------------------------------------------------------------------------------- 1 | ---------------------------------------- 2 | -- INPUTS 3 | -- location: neighborhood 4 | ---------------------------------------- 5 | UPDATE neighborhood_ways SET ft_seg_stress = NULL, tf_seg_stress = NULL 6 | WHERE functional_class = 'track'; 7 | 8 | UPDATE neighborhood_ways 9 | SET ft_seg_stress = 1, 10 | tf_seg_stress = 1 11 | WHERE functional_class = 'track'; 12 | -------------------------------------------------------------------------------- /src/angularjs/.bowerrc: -------------------------------------------------------------------------------- 1 | { 2 | "directory": "bower_components", 3 | "registry": "https://registry.bower.io", 4 | "strict-ssl": false 5 | } 6 | -------------------------------------------------------------------------------- /src/angularjs/.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /src/angularjs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "plugins": ["angular"], 4 | "env": { 5 | "browser": true, 6 | "jasmine": true 7 | }, 8 | "globals": { 9 | "angular": true, 10 | "module": true, 11 | "URI": true, 12 | "_": true, 13 | "L": true, 14 | "inject": true, 15 | "gtag": true 16 | }, 17 | "rules": { 18 | "angular/controller-as-vm": [2, "ctl"], 19 | "no-unreachable": 0 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/angularjs/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | bower_components/ 3 | coverage/ 4 | .sass-cache/ 5 | .idea/ 6 | .tmp/ 7 | dist/ 8 | -------------------------------------------------------------------------------- /src/angularjs/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:6.17-stretch 2 | 3 | MAINTAINER Azavea 4 | 5 | ENV ANGULAR_DIR /opt/pfb/angularjs 6 | ENV NODE_OPTIONS --use-openssl-ca 7 | 8 | RUN echo "deb http://archive.debian.org/debian stretch main" > /etc/apt/sources.list 9 | 10 | RUN apt-get update && apt-get install -y rsync \ 11 | && rm -rf /var/lib/apt/lists/* 12 | 13 | RUN npm install -g bower gulp@3.9.0 14 | 15 | WORKDIR /opt/pfb/angularjs 16 | COPY package.json ${ANGULAR_DIR}/package.json 17 | RUN npm install 18 | 19 | COPY bower.json ${ANGULAR_DIR}/bower.json 20 | COPY .bowerrc ${ANGULAR_DIR}/.bowerrc 21 | RUN bower install --allow-root --config.interactive=false 22 | 23 | COPY .eslintrc ${ANGULAR_DIR}/.eslintrc 24 | COPY gulp ${ANGULAR_DIR}/gulp 25 | COPY src ${ANGULAR_DIR}/src 26 | 27 | COPY gulpfile.js ${ANGULAR_DIR}/gulpfile.js 28 | 29 | RUN gulp build 30 | -------------------------------------------------------------------------------- /src/angularjs/bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pfb", 3 | "version": "0.0.0", 4 | "dependencies": { 5 | "angular": "1.5.11", 6 | "angular-animate": "1.5.11", 7 | "angular-loading-bar": "0.9.0", 8 | "angular-aria": "1.5.11", 9 | "angular-cookies": "1.5.11", 10 | "highlightjs": "9.9.0", 11 | "angular-highlightjs": "0.7.0", 12 | "angular-messages": "1.5.11", 13 | "angular-sanitize": "1.5.11", 14 | "jquery": "2.2.4", 15 | "angular-resource": "1.5.11", 16 | "angular-ui-router": "0.4.2", 17 | "angular-toastr": "2.1.1", 18 | "leaflet": "1.0.3", 19 | "leaflet-groupedlayercontrol": "0.6.0", 20 | "leaflet.markercluster": "1.3.0", 21 | "moment": "2.17.1", 22 | "animate.css": "3.5.2", 23 | "angular-bootstrap": "1.3.3", 24 | "bootstrap-sass-official": "3.3.6", 25 | "urijs": "1.18.7", 26 | "lodash": "4.6.1", 27 | "ng-file-upload": "^12.2.13", 28 | "select2": "~3.4.5" 29 | }, 30 | "devDependencies": { 31 | "angular-mocks": "1.5.11", 32 | "bootstrap": "^3.3.6" 33 | }, 34 | "overrides": {}, 35 | "resolutions": { 36 | "jquery": "2.2.4", 37 | "angular": "1.5.11" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/angularjs/gulp/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "globals": { 6 | "require": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/angularjs/gulp/conf.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This file contains the variables used in other gulp files 3 | * which defines tasks 4 | * By design, we only put there very generic config values 5 | * which are used in several places to keep good readability 6 | * of the tasks 7 | */ 8 | 9 | var gutil = require('gulp-util'); 10 | 11 | /** 12 | * The main paths of your project handle these with care 13 | */ 14 | exports.paths = { 15 | src: 'src', 16 | bower: 'bower_components', 17 | dist: 'dist', 18 | tmp: '.tmp', 19 | e2e: 'e2e' 20 | }; 21 | 22 | /** 23 | * Wiredep is the lib which inject bower dependencies in your project 24 | * Mainly used to inject script tags in the index.html but also used 25 | * to inject css preprocessor deps and js files in karma 26 | */ 27 | exports.wiredep = { 28 | exclude: [/\/bootstrap\.js$/, /\/bootstrap-sass\/.*\.js/, /\/bootstrap\.css/], 29 | directory: 'bower_components' 30 | }; 31 | 32 | /** 33 | * Common implementation for an error handler of a Gulp plugin 34 | */ 35 | exports.errorHandler = function(title) { 36 | 'use strict'; 37 | 38 | return function(err) { 39 | gutil.log(gutil.colors.red('[' + title + ']'), err.toString()); 40 | this.emit('end'); 41 | process.exit(1); 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/angularjs/gulp/docs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var ngdocs = require('gulp-ngdocs'); 8 | 9 | 10 | gulp.task('docs', function () { 11 | var options = { 12 | scripts: [ 13 | 'bower/angular/angular.min.js', 14 | 'bower/angular-animate/angular-animate.min.js' 15 | ], 16 | title: 'PfB Network Connectivity Angular Docs', 17 | html5Mode: false 18 | }; 19 | 20 | return gulp.src(path.join(conf.paths.src, 'app/**/*.js')) 21 | .pipe(ngdocs.process(options)) 22 | .pipe(gulp.dest('./docs')); 23 | }); 24 | -------------------------------------------------------------------------------- /src/angularjs/gulp/scripts.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | var $ = require('gulp-load-plugins')(); 10 | 11 | 12 | gulp.task('scripts-reload', function() { 13 | return buildScripts() 14 | .pipe(browserSync.stream()); 15 | }); 16 | 17 | gulp.task('scripts', function() { 18 | return buildScripts(); 19 | }); 20 | 21 | function buildScripts() { 22 | return gulp.src(path.join(conf.paths.src, '/app/**/*.js')) 23 | .pipe($.eslint()) 24 | .pipe($.eslint.format()) 25 | .pipe($.eslint.failAfterError()) 26 | .pipe($.size()); 27 | }; 28 | -------------------------------------------------------------------------------- /src/angularjs/gulp/watch.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var gulp = require('gulp'); 5 | var conf = require('./conf'); 6 | 7 | var browserSync = require('browser-sync'); 8 | 9 | function isOnlyChange(event) { 10 | return event.type === 'changed'; 11 | } 12 | 13 | gulp.task('watch', ['inject'], function () { 14 | 15 | gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject-reload']); 16 | 17 | gulp.watch([ 18 | path.join(conf.paths.src, '/styles/**/*.css'), 19 | path.join(conf.paths.src, '/styles/**/*.scss') 20 | ], function(event) { 21 | if(isOnlyChange(event)) { 22 | gulp.start('styles-reload'); 23 | } else { 24 | gulp.start('inject-reload'); 25 | } 26 | }); 27 | 28 | gulp.watch(path.join(conf.paths.src, '/app/**/*.js'), function(event) { 29 | if(isOnlyChange(event)) { 30 | gulp.start('scripts-reload'); 31 | } else { 32 | gulp.start('inject-reload'); 33 | } 34 | }); 35 | 36 | gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) { 37 | browserSync.reload(event.path); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /src/angularjs/gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Welcome to your gulpfile! 3 | * The gulp tasks are splitted in several files in the gulp directory 4 | * because putting all here was really too long 5 | */ 6 | 7 | 'use strict'; 8 | 9 | var gulp = require('gulp'); 10 | var wrench = require('wrench'); 11 | var Promise = require('es6-promise').Promise; 12 | var eslint = require('gulp-eslint'); 13 | 14 | /** 15 | * This will load all js or coffee files in the gulp directory 16 | * in order to load all gulp tasks 17 | */ 18 | wrench.readdirSyncRecursive('./gulp').filter(function(file) { 19 | return (/\.(js|coffee)$/i).test(file); 20 | }).map(function(file) { 21 | require('./gulp/' + file); 22 | }); 23 | 24 | 25 | /** 26 | * Default task clean temporaries directories and launch the 27 | * main optimization build task 28 | */ 29 | gulp.task('default', ['clean'], function () { 30 | gulp.start('build'); 31 | }); 32 | 33 | gulp.task('lint', [], function () { 34 | return gulp.src(['src/**/*.js']) 35 | .pipe(eslint()) 36 | .pipe(eslint.format()) 37 | .pipe(eslint.failAfterError()); 38 | }); 39 | -------------------------------------------------------------------------------- /src/angularjs/src/app/analysis-jobs/create/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('pfb.analysisJobs.create', []); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/angularjs/src/app/analysis-jobs/detail/analysis-jobs-detail.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc controller 3 | * @name pfb.analysis-jobs.detail.controller:AnalysisJobDetailController 4 | * 5 | * @description 6 | * Controller for showing details about an analysis job 7 | * 8 | */ 9 | (function() { 10 | 'use strict'; 11 | 12 | /** @ngInject */ 13 | function AnalysisJobDetailController($stateParams, AnalysisJob) { 14 | var ctl = this; 15 | 16 | initialize(); 17 | 18 | function initialize() { 19 | ctl.job = null; 20 | ctl.cancel = cancel; 21 | ctl.getAnalysisJob = getAnalysisJob; 22 | 23 | getAnalysisJob($stateParams.uuid); 24 | } 25 | 26 | function cancel(jobId) { 27 | AnalysisJob.cancel({uuid: jobId}).$promise.then(function() { 28 | getAnalysisJob(jobId); 29 | }); 30 | } 31 | 32 | function getAnalysisJob(jobId) { 33 | AnalysisJob.get({uuid: jobId}).$promise.then(function(data) { 34 | ctl.job = data; 35 | }); 36 | AnalysisJob.results({uuid: jobId}).$promise.then(function(data) { 37 | ctl.results = data; 38 | }); 39 | } 40 | } 41 | 42 | angular 43 | .module('pfb.analysisJobs.detail') 44 | .controller('AnalysisJobDetailController', AnalysisJobDetailController); 45 | })(); 46 | -------------------------------------------------------------------------------- /src/angularjs/src/app/analysis-jobs/detail/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('pfb.analysisJobs.detail', []); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/angularjs/src/app/analysis-jobs/import/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('pfb.analysisJobs.import', []); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/angularjs/src/app/analysis-jobs/list/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.analysisJobs.list', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/analysis-jobs/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.analysisJobs', 5 | ['pfb.analysisJobs.constants', 6 | 'pfb.analysisJobs.list', 7 | 'pfb.analysisJobs.create', 8 | 'pfb.analysisJobs.detail', 9 | 'pfb.analysisJobs.import']); 10 | })(); 11 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/analysis-jobs/analysis-jobs-status.filter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc filter 3 | * @name pfb.analysis-jobs.status:displayStatus 4 | * 5 | * @description 6 | * Transforms analysis job status into user friendly long description string 7 | */ 8 | (function () { 9 | 'use strict'; 10 | 11 | /* ngInject */ 12 | function displayStatus($log, JOB_STATUSES) { 13 | 14 | return function (input) { 15 | if (input in JOB_STATUSES) { 16 | return JOB_STATUSES[input].long; 17 | } 18 | 19 | $log.warn(input + ' is not a recognized job status'); 20 | return input; 21 | }; 22 | } 23 | 24 | angular.module('pfb.components.analysis-jobs') 25 | .filter('displayStatus', displayStatus); 26 | })(); 27 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/analysis-jobs/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.components.analysis-jobs', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/auth/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.components.auth', []); 5 | 6 | })(); 7 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/countries.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc service 3 | * @name pfb.components.stats:Country 4 | * @description 5 | * Resource for countries 6 | */ 7 | (function() { 8 | 'use strict'; 9 | 10 | /* @ngInject */ 11 | function Country($resource) { 12 | return $resource('/api/countries/', {}, { 13 | cache: true, 14 | query: { 15 | method: 'GET', 16 | isArray: true 17 | } 18 | }); 19 | } 20 | 21 | angular.module('pfb.components') 22 | .factory('Country', Country); 23 | })(); 24 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/filters/module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular.module('pfb.components.filters', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/filters/user-filter.html: -------------------------------------------------------------------------------- 1 | 2 | Email 3 | Name 4 | 5 | 12 | 13 | 14 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/footer/footer.directive.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc directive 3 | * @name pfb.footer.directive:pfbFooter 4 | * @restrict 'E' 5 | * 6 | * @description 7 | * Top level navigation bar for pfb application 8 | */ 9 | 10 | (function() { 11 | 'use strict'; 12 | 13 | /** @ngInject */ 14 | function FooterController(AuthService) { 15 | var ctl = this; 16 | 17 | initialize(); 18 | 19 | function initialize() { 20 | ctl.logout = AuthService.logout; 21 | // if user ID cookie is set, assume user is logged in 22 | ctl.loggedIn = !!AuthService.getUserId(); 23 | } 24 | } 25 | 26 | function pfbFooter() { 27 | var directive = { 28 | restrict: 'E', 29 | templateUrl: 'app/components/footer/footer.html', 30 | controller: 'FooterController', 31 | controllerAs: 'footer', 32 | bindToController: true 33 | }; 34 | 35 | return directive; 36 | } 37 | 38 | 39 | angular 40 | .module('pfb') 41 | .controller('FooterController', FooterController) 42 | .directive('pfbFooter', pfbFooter); 43 | 44 | })(); 45 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/footer/footer.html: -------------------------------------------------------------------------------- 1 | 21 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/map/module.js: -------------------------------------------------------------------------------- 1 | 2 | (function() { 3 | 'use strict'; 4 | 5 | angular.module('pfb.components.map', []); 6 | })(); 7 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/modals/confirmation-modal.html: -------------------------------------------------------------------------------- 1 | 4 | 7 | 12 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/modals/confirmation-modal.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /* ngInject */ 5 | function ConfirmationModal($uibModal) { 6 | var module = { 7 | open: open 8 | }; 9 | return module; 10 | 11 | // Return uibModalInstance object 12 | function open(params) { 13 | var uibModalInstance = $uibModal.open({ 14 | templateUrl: 'app/components/modals/confirmation-modal.html', 15 | controller: 'ModalInstanceController', 16 | controllerAs: 'modal', 17 | bindToController: true, 18 | size: 'md', 19 | resolve: { 20 | params: function () { 21 | return params; 22 | } 23 | } 24 | }); 25 | 26 | return uibModalInstance; 27 | } 28 | } 29 | 30 | angular.module('pfb.components.modals') 31 | .service('ConfirmationModal', ConfirmationModal); 32 | 33 | })(); 34 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/modals/modal-instance-controller.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | /** 5 | * A controller for a uibModal instance. 6 | * Use 'resolve' in your $uibModal.open() call to fill 'params' with whatever parameters 7 | * you want to use inside the modal. 8 | */ 9 | /* ngInject */ 10 | function ModalInstanceController($uibModalInstance, params) { 11 | var ctl = this; 12 | ctl.ok = ok; 13 | ctl.cancel = cancel; 14 | 15 | // params needs to be set on construction. If delayed until $onInit, the initial template 16 | // render doesn't have the values it needs 17 | ctl.params = params; 18 | 19 | function ok () { 20 | $uibModalInstance.close(); 21 | } 22 | 23 | function cancel () { 24 | $uibModalInstance.dismiss('cancel'); 25 | } 26 | } 27 | 28 | angular.module('pfb.components.modals') 29 | .controller('ModalInstanceController', ModalInstanceController); 30 | 31 | })(); 32 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/modals/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.components.modals', ['ui.bootstrap']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.components', 5 | ['pfb.components.analysis-jobs', 6 | 'pfb.components.auth', 7 | 'pfb.components.filters', 8 | 'pfb.components.map', 9 | 'pfb.components.modals', 10 | 'pfb.components.thumbnailMap']); 11 | })(); 12 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/neighborhoods.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc service 3 | * @name pfb.components.stats:Neighborhood 4 | * 5 | * @description 6 | * Resource for neighborhoods 7 | */ 8 | (function() { 9 | 'use strict'; 10 | 11 | /* @ngInject */ 12 | function Neighborhood($resource) { 13 | return $resource('/api/neighborhoods/:uuid/', {uuid: '@uuid'}, { 14 | 'query': { 15 | method: 'GET', 16 | isArray: false 17 | }, 18 | 'all': { 19 | method: 'GET', 20 | isArray: false, 21 | params: { 22 | limit: 'all' 23 | } 24 | }, 25 | 'geojson': { 26 | method: 'GET', 27 | isArray: false, 28 | url: '/api/neighborhoods_geojson/' 29 | }, 30 | 'bounds': { 31 | method: 'GET', 32 | isArray: false, 33 | url: '/api/neighborhoods_bounds_geojson/:uuid/' 34 | } 35 | }); 36 | } 37 | 38 | angular.module('pfb.components') 39 | .factory('Neighborhood', Neighborhood); 40 | })(); 41 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/organizations.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc service 3 | * @name pfb.components.organization:Organization 4 | * 5 | * @description 6 | * Resource for Organization 7 | */ 8 | (function() { 9 | 'use strict'; 10 | 11 | /* @ngInject */ 12 | function Organization($resource) { 13 | var module = $resource('/api/organizations/:uuid/', {uuid: '@uuid'}, { 14 | 'update': { 15 | method: 'PUT' 16 | }, 17 | 'list': { 18 | method: 'GET', 19 | url: '/api/organizations/', 20 | isArray: true 21 | } 22 | }); 23 | 24 | module.orgTypes = { 25 | ADMIN: 'Administrator Organization', 26 | SUBSCRIBER: 'Subscription' 27 | }; 28 | 29 | return module; 30 | } 31 | 32 | angular.module('pfb') 33 | .factory('Organization', Organization); 34 | })(); 35 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/pagination.service.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc service 3 | * @name pfb.components.Pagination:Pagination 4 | * 5 | * @description 6 | * Handles parsing next/previous links for params to aid pagination 7 | */ 8 | (function() { 9 | 'use strict'; 10 | 11 | /* @ngInject */ 12 | function Pagination() { 13 | 14 | var module = { 15 | getLinkParams: getLinkParams 16 | }; 17 | 18 | return module; 19 | 20 | function getLinkParams(url) { 21 | if (!url) { 22 | return null; 23 | } else { 24 | var uri = URI(url); 25 | return uri.search(true); 26 | } 27 | } 28 | } 29 | 30 | angular.module('pfb.components') 31 | .factory('Pagination', Pagination); 32 | })(); 33 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/thumbnail-map/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.components.thumbnailMap', ['pfb.components.map']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/thumbnail-map/thumbnail-map.html: -------------------------------------------------------------------------------- 1 | 2 |
10 |
11 | -------------------------------------------------------------------------------- /src/angularjs/src/app/components/users.service.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | /** 5 | * @ngdoc service 6 | * @name pfb.components.User:User 7 | * 8 | * @description 9 | * Resource for user 10 | */ 11 | /* @ngInject */ 12 | function User($resource) { 13 | 14 | return $resource('/api/users/:uuid/', { 15 | uuid: '@uuid' 16 | }, { 17 | 'update': { 18 | method: 'PUT' 19 | }, 20 | 'query': { 21 | method: 'GET', 22 | isArray: false 23 | } 24 | }); 25 | } 26 | 27 | /** 28 | * @ngdoc service 29 | * @name pfb.components.UserRoles:UserRoles 30 | * 31 | * @description 32 | * Resource for user roles 33 | */ 34 | function UserRoles() { 35 | var roles = ['Viewer', 'Administrator', 'Organization Administrator']; 36 | var roleFilters = { 37 | 'Viewer': 'VIEWER', 38 | 'Administrator': 'ADMIN', 39 | 'Organization Administrator': 'ORGADMIN' 40 | }; 41 | var module = { 42 | roles: roles, 43 | roleFilters: roleFilters 44 | }; 45 | return module; 46 | } 47 | 48 | angular.module('pfb.components') 49 | .factory('User', User) 50 | .factory('UserRoles', UserRoles); 51 | })(); 52 | -------------------------------------------------------------------------------- /src/angularjs/src/app/help/help.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 |

Help

7 | 8 |
9 |
10 |

Authentication

11 |

The Account Settings page contains a field for an API token. If no API token exists, click the Refresh link to generate a new token. Once a token is created, authenticating API requests involves supplying the provided token via the Authorization HTTP header. Below is an example using curl:

12 | 13 |
14 | curl -H "Authorization: Token TOKEN" \
15 |      -X GET "{{help.protocol}}://{{help.host}}/api/boundary-results/"
16 |
17 |
18 |
19 |
20 |
21 | -------------------------------------------------------------------------------- /src/angularjs/src/app/help/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.help', ['hljs']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/home/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.home', ['pfb.components']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/home/neighborhood-map.html: -------------------------------------------------------------------------------- 1 |

2 | 3 | Is your place on the map? 4 |

5 |
6 |
7 |
8 |
{{ ctl.count || "--" }}
9 |
Places
10 | Find your Place 11 | 12 | 13 |
14 |
15 | 16 |
22 |
23 |
24 | -------------------------------------------------------------------------------- /src/angularjs/src/app/index.constants.js: -------------------------------------------------------------------------------- 1 | /* global moment:false */ 2 | (function() { 3 | 'use strict'; 4 | 5 | angular 6 | .module('pfb') 7 | .constant('moment', moment); 8 | 9 | })(); 10 | -------------------------------------------------------------------------------- /src/angularjs/src/app/index.module.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('pfb', [ 6 | 'angular-loading-bar', 'pfb.login', 'pfb.analysisJobs', 'pfb.help', 'pfb.home', 7 | 'pfb.utils', 'pfb.places', 'pfb.passwordReset', 'pfb.users', 'pfb.organizations', 8 | 'pfb.neighborhoods', 9 | 'ngAnimate', 'ngCookies', 'ngSanitize', 'ngMessages', 'ngAria', 10 | 'ngResource', 'ui.router', 'toastr', 'ui.bootstrap', 'pfb.components']); 11 | 12 | })(); 13 | -------------------------------------------------------------------------------- /src/angularjs/src/app/index.run.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | 'use strict'; 3 | 4 | angular 5 | .module('pfb') 6 | .run(runBlock); 7 | 8 | /** @ngInject */ 9 | function runBlock($rootScope, $window) { 10 | // Ignore on-watch rule, since we're attaching to $rootScope 11 | /*eslint angular/on-watch: 0*/ 12 | $rootScope.$on('$stateChangeSuccess', function ($event, toState) { 13 | var event = { 14 | 'app_name': 'PFB BNA Score', 15 | 'screen_name': toState.name, 16 | 'environment': $window.location.hostname 17 | } 18 | gtag('event', 'screen_view', event); 19 | }); 20 | } 21 | })(); 22 | -------------------------------------------------------------------------------- /src/angularjs/src/app/login/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.login', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/login/user-login.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc controller 3 | * @name pfb.login.user-login.controller:LoginController 4 | * 5 | * @description 6 | * Controller handling logging a user in 7 | * 8 | */ 9 | (function() { 10 | 'use strict'; 11 | 12 | /** @ngInject */ 13 | function LoginController($log, $state, toastr, AuthService) { 14 | var ctl = this; 15 | 16 | initialize(); 17 | 18 | function initialize() { 19 | ctl.login = login; 20 | if (AuthService.getEmail()) { 21 | $state.go('admin.analysis-jobs.list'); 22 | } 23 | } 24 | 25 | function login() { 26 | AuthService.user = AuthService.login( 27 | {'email': ctl.email, 'password': ctl.password} 28 | ).then(function() { 29 | $state.go('admin.analysis-jobs.list'); 30 | }).catch(function() { 31 | toastr.error('Unable to login with credentials', 'Error'); 32 | }); 33 | } 34 | } 35 | 36 | angular 37 | .module('pfb.login') 38 | .controller('LoginController', LoginController); 39 | 40 | })(); 41 | -------------------------------------------------------------------------------- /src/angularjs/src/app/neighborhoods/detail/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('pfb.neighborhoods.detail', ['ngFileUpload']); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/angularjs/src/app/neighborhoods/detail/neighborhood-detail-map.html: -------------------------------------------------------------------------------- 1 | 2 |
10 |
11 | -------------------------------------------------------------------------------- /src/angularjs/src/app/neighborhoods/list/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.neighborhoods.list', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/neighborhoods/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.neighborhoods', 5 | ['pfb.neighborhoods.list', 'pfb.neighborhoods.detail']); 6 | })(); 7 | -------------------------------------------------------------------------------- /src/angularjs/src/app/organizations/detail/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.organizations.detail', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/organizations/list/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.organizations.list', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/organizations/list/organizations-list.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc controller 3 | * @name pfb.organizations.list.organizations-list.controller:OrganizationListController 4 | * 5 | * @description 6 | * Controller for Organization list page 7 | * 8 | */ 9 | (function() { 10 | 'use strict'; 11 | 12 | /** @ngInject */ 13 | function OrganizationListController(Organization) { 14 | var ctl = this; 15 | ctl.organizations = []; 16 | ctl.orgTypes = Organization.orgTypes; 17 | 18 | initialize(); 19 | 20 | function initialize() { 21 | ctl.orgs = Organization.query(); 22 | } 23 | } 24 | 25 | angular 26 | .module('pfb.organizations.list') 27 | .controller('OrganizationListController', OrganizationListController); 28 | })(); 29 | -------------------------------------------------------------------------------- /src/angularjs/src/app/organizations/list/organizations-list.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 |
5 |
6 | 7 |

Organization Management

8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 23 | 24 | 25 |
NameType
{{org.name}}{{orgList.orgTypes[org.orgType]}} 21 | 22 |
26 |
27 |
28 |
29 | -------------------------------------------------------------------------------- /src/angularjs/src/app/organizations/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.organizations', 5 | ['pfb.organizations.list', 6 | 'pfb.organizations.detail']); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/angularjs/src/app/password-reset/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.passwordReset', 5 | ['pfb.passwordReset.request', 6 | 'pfb.passwordReset.reset']); 7 | })(); 8 | -------------------------------------------------------------------------------- /src/angularjs/src/app/password-reset/request/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.passwordReset.request', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/password-reset/request/password-reset-request.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc controller 3 | * @name pfb.password-reset.controller:PasswordResetController 4 | * 5 | * @description 6 | * Controller for password reset request page 7 | * 8 | */ 9 | (function() { 10 | 'use strict'; 11 | 12 | /** @ngInject */ 13 | function PasswordResetRequestController($log, PasswordResetService, toastr) { 14 | var ctl = this; 15 | 16 | initialize(); 17 | 18 | function initialize() { 19 | ctl.requestReset = requestReset; 20 | } 21 | 22 | function requestReset() { 23 | PasswordResetService.requestPasswordReset(ctl.email); 24 | toastr.info('Password reset successfully requested'); 25 | } 26 | } 27 | 28 | angular 29 | .module('pfb.passwordReset.request') 30 | .controller('PasswordResetRequestController', PasswordResetRequestController); 31 | })(); 32 | -------------------------------------------------------------------------------- /src/angularjs/src/app/password-reset/request/password-reset-request.html: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | 28 |
29 |
30 |
31 | -------------------------------------------------------------------------------- /src/angularjs/src/app/password-reset/reset/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.passwordReset.reset', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/password-reset/reset/password-reset.controller.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @ngdoc controller 3 | * @name pfb.password-reset.controller:PasswordResetController 4 | * 5 | * @description 6 | * Controller for password reset page 7 | * 8 | */ 9 | (function() { 10 | 'use strict'; 11 | 12 | /** @ngInject */ 13 | function PasswordResetController($stateParams, $state, toastr, PasswordResetService) { 14 | var ctl = this; 15 | 16 | initialize(); 17 | 18 | function initialize() { 19 | ctl.resetPassword = resetPassword; 20 | ctl.formSubmitted = false; 21 | } 22 | 23 | function resetPassword() { 24 | var reset = PasswordResetService.resetPassword(ctl.password, $stateParams.token); 25 | ctl.formSubmitted = true; 26 | reset.then(function() { 27 | toastr.info('Password reset succesfully, return to login to use new password'); 28 | }).catch(function() { 29 | // TODO: Return more meaningful error messages based on response 30 | toastr.error('Unable to reset password with provided token and password.'); 31 | $state.go('request-password-reset'); 32 | }); 33 | } 34 | } 35 | 36 | angular 37 | .module('pfb.passwordReset') 38 | .controller('PasswordResetController', PasswordResetController); 39 | })(); 40 | -------------------------------------------------------------------------------- /src/angularjs/src/app/places/compare/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | angular.module('pfb.places.compare', []); 4 | })(); 5 | -------------------------------------------------------------------------------- /src/angularjs/src/app/places/detail/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.places.detail', ['pfb.components.map']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/places/detail/place-map.html: -------------------------------------------------------------------------------- 1 | 2 |
12 |
13 | -------------------------------------------------------------------------------- /src/angularjs/src/app/places/list/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.places.list', ['pfb.components.map']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/places/list/places-list-map.html: -------------------------------------------------------------------------------- 1 | 2 |
8 |
9 | -------------------------------------------------------------------------------- /src/angularjs/src/app/places/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.places', 5 | ['pfb.places.list', 'pfb.places.detail', 'pfb.places.compare']); 6 | })(); 7 | -------------------------------------------------------------------------------- /src/angularjs/src/app/users/detail/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.users.detail', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/users/list/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.users.list', []); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/users/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.users', ['pfb.users.list', 'pfb.users.detail']); 5 | })(); 6 | -------------------------------------------------------------------------------- /src/angularjs/src/app/utils/module.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | 'use strict'; 3 | 4 | angular.module('pfb.utils', []); 5 | })(); -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/fontello/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Font license info 2 | 3 | 4 | ## Font Awesome 5 | 6 | Copyright (C) 2016 by Dave Gandy 7 | 8 | Author: Dave Gandy 9 | License: SIL () 10 | Homepage: http://fortawesome.github.com/Font-Awesome/ 11 | 12 | 13 | ## Entypo 14 | 15 | Copyright (C) 2012 by Daniel Bruce 16 | 17 | Author: Daniel Bruce 18 | License: SIL (http://scripts.sil.org/OFL) 19 | Homepage: http://www.entypo.com 20 | 21 | 22 | ## Iconic 23 | 24 | Copyright (C) 2012 by P.J. Onori 25 | 26 | Author: P.J. Onori 27 | License: SIL (http://scripts.sil.org/OFL) 28 | Homepage: http://somerandomdude.com/work/iconic/ 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/fontello/font/fontello.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/fontello/font/fontello.eot -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/fontello/font/fontello.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/fontello/font/fontello.ttf -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/fontello/font/fontello.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/fontello/font/fontello.woff -------------------------------------------------------------------------------- /src/angularjs/src/assets/fonts/fontello/font/fontello.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/fonts/fontello/font/fontello.woff2 -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/.gitkeep -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/bna-icon-dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/bna-icon-dark.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/bna-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/bna-icon.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/bna-logo-2021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/bna-logo-2021.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/bna-logo-horizontal-2021.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/bna-logo-horizontal-2021.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/bna-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/bna-logo.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/fatality-active-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/fatality-active-icon.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/fatality-bike-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/fatality-bike-icon.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/fatality-motor-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/fatality-motor-icon.png -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/home-header-2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/home-header-2.jpg -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/home-header-3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/home-header-3.jpg -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/home-header.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/home-header.jpg -------------------------------------------------------------------------------- /src/angularjs/src/assets/images/web-racing-stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/angularjs/src/assets/images/web-racing-stripe.png -------------------------------------------------------------------------------- /src/angularjs/src/styles/base/_animation.scss: -------------------------------------------------------------------------------- 1 | .fade { 2 | opacity: 0; 3 | transition: .15s linear opacity; 4 | &.in { 5 | opacity: 1; 6 | } 7 | } 8 | 9 | .animationIf.ng-enter, 10 | .animationIf.ng-leave { 11 | transition: opacity ease-in-out 0.5s; 12 | } 13 | .animationIf.ng-enter, 14 | .animationIf.ng-leave.ng-leave-active { 15 | opacity: 0; 16 | } 17 | .animationIf.ng-leave, 18 | .animationIf.ng-enter.ng-enter-active { 19 | opacity: 1; 20 | } 21 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/base/_base.scss: -------------------------------------------------------------------------------- 1 | *, *:after, *:before { 2 | box-sizing: border-box; 3 | } 4 | 5 | html { 6 | font-size: 62.5%; 7 | font-family: 'Cabin Condensed', sans-serif; 8 | height: 100%; 9 | } 10 | 11 | body { 12 | font-size: 14px; 13 | font-size: 1.4rem; 14 | color: $text-base; 15 | height: 100%; 16 | } 17 | 18 | a { 19 | text-decoration: none; 20 | font-weight: 600; 21 | color: $brand-primary; 22 | 23 | &:hover { 24 | color: darken($brand-primary, 10%); 25 | cursor: pointer; 26 | } 27 | 28 | &:disabled, &.disabled, &[disabled] { 29 | opacity: .5; 30 | cursor: not-allowed; 31 | 32 | &:hover { 33 | color: darken($brand-primary, 10%); 34 | } 35 | } 36 | } 37 | 38 | hr { 39 | margin-top: 1rem; 40 | margin-bottom: 1rem; 41 | border: 0; 42 | border-top: 1px solid #797979; 43 | clear: both; 44 | } 45 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/base/_list.scss: -------------------------------------------------------------------------------- 1 | dl { 2 | @extend %clearfix; 3 | } 4 | 5 | dl > dt { 6 | font-weight: 600; 7 | clear: both; 8 | margin: 5px 0; 9 | } 10 | 11 | dl > dd { 12 | margin-bottom: 10px; 13 | margin-left: 0; 14 | } 15 | 16 | ul { 17 | li { 18 | margin-top: 1rem; 19 | margin-bottom: 1rem; 20 | } 21 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/base/_typography.scss: -------------------------------------------------------------------------------- 1 | @mixin heading-tags { 2 | h1, h2, h3, h4, h5, h6, 3 | .h1, .h2, .h3, .h4, .h5, .h6 { 4 | @content; 5 | } 6 | } 7 | 8 | @include heading-tags { 9 | margin-bottom: 1rem; 10 | line-height: 1.2; 11 | color: #000; 12 | font-weight: bold; 13 | letter-spacing: 1px; 14 | } 15 | 16 | h1, h2, h3, 17 | .h1, .h2, .h3 { 18 | margin-top: 2rem; 19 | margin-bottom: 3.5rem; 20 | } 21 | 22 | h4, h5, h6, 23 | .h4, .h5, .h6 { 24 | margin-top: 1.3rem; 25 | } 26 | 27 | h1, .h1 { 28 | font-size: 4rem; 29 | } 30 | 31 | h2, .h2 { 32 | font-size: 2.8rem; 33 | } 34 | 35 | h3, .h3 { 36 | font-size: 2.2rem; 37 | } 38 | 39 | h4, .h4 { 40 | font-size: 1.8rem; 41 | } 42 | 43 | h5, .h5 { 44 | font-size: 1.6rem; 45 | } 46 | 47 | h6, .h6 { 48 | font-size: 1.6rem; 49 | } 50 | 51 | p, .p { 52 | font-size: 1.6rem; 53 | font-weight: 400; 54 | line-height: 1.6; 55 | margin-top: 1rem; 56 | margin-bottom: 2.5rem; 57 | } 58 | 59 | .font-size-small { 60 | font-size: 1.4rem; 61 | } 62 | 63 | .text-uppercase { 64 | text-transform: uppercase; 65 | } 66 | 67 | .text-capitalize { 68 | text-transform: capitalize; 69 | } 70 | 71 | .text-lowercase { 72 | text-transform: lowercase; 73 | } 74 | 75 | .text-center { 76 | text-align: center; 77 | } 78 | 79 | .text-right { 80 | text-align: right; 81 | } 82 | 83 | .text-left { 84 | text-align: left; 85 | } 86 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_image.scss: -------------------------------------------------------------------------------- 1 | img { 2 | max-width: 100%; 3 | } 4 | 5 | .center-img { 6 | display: block; 7 | margin: auto; 8 | max-width: 100%; 9 | height: auto; 10 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_metrics.scss: -------------------------------------------------------------------------------- 1 | .metric-list { 2 | margin: 0; 3 | list-style-type: none; 4 | padding: 0; 5 | position: relative; 6 | z-index: 10; 7 | 8 | li { 9 | padding: 2rem; 10 | margin: 0; 11 | border-top: 1px solid #ddd; 12 | font-size: 1.8rem; 13 | 14 | &.subscore { 15 | padding-left: 4rem; 16 | font-size: 1.4rem; 17 | } 18 | } 19 | 20 | .network-score { 21 | float: right; 22 | margin-top: -5px !important; 23 | } 24 | 25 | .list-button { 26 | text-align: center; 27 | } 28 | } 29 | 30 | .metric-details { 31 | flex: 1; 32 | padding: 2rem; 33 | 34 | h1, h2, h3, h4 { 35 | margin: 0; 36 | } 37 | 38 | p:last-of-type { 39 | margin-bottom: 0; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_nav.scss: -------------------------------------------------------------------------------- 1 | nav { 2 | 3 | a { 4 | display: inline-block; 5 | padding: 10px; 6 | } 7 | } 8 | 9 | nav.social { 10 | font-size: 1.8rem; 11 | 12 | a:not(:hover) { 13 | color: #fff; 14 | } 15 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_network-score.scss: -------------------------------------------------------------------------------- 1 | .network-score { 2 | margin-top: 1rem; 3 | margin-bottom: 1rem; 4 | font-size: 4rem; 5 | font-weight: bold; 6 | 7 | &.large { 8 | font-size: 7rem; 9 | margin-bottom: 0; 10 | } 11 | 12 | &.small { 13 | font-size: 2.5rem; 14 | margin: 0; 15 | } 16 | 17 | .h3, h3 { 18 | margin: 0; 19 | } 20 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_panel.scss: -------------------------------------------------------------------------------- 1 | .panel { 2 | background-color: #fff; 3 | position: relative; 4 | border: 1px solid #ddd; 5 | } 6 | 7 | .panel-body { 8 | padding: 1.5rem; 9 | } 10 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_table.scss: -------------------------------------------------------------------------------- 1 | .table { 2 | width: 100%; 3 | border-collapse: collapse; 4 | border: 1px solid #ddd; 5 | margin-bottom: 1rem; 6 | 7 | thead { 8 | text-align: left; 9 | 10 | th, td { 11 | font-weight: 600; 12 | } 13 | } 14 | 15 | tbody { 16 | text-align: left; 17 | 18 | > tr:hover { 19 | background-color: #f3f3f3; 20 | } 21 | } 22 | 23 | th, 24 | td { 25 | border-bottom: 1px solid #ddd; 26 | padding: 2rem 1.5rem; 27 | } 28 | } 29 | 30 | .table-responsive { 31 | overflow: auto; 32 | white-space: nowrap; 33 | } 34 | 35 | .table-mobile-stacked { 36 | @include respond-to(xs) { 37 | 38 | thead { 39 | display: none; 40 | } 41 | 42 | tr { 43 | border-bottom: 1px solid #ddd; 44 | } 45 | 46 | tbody [data-th] { 47 | display: block; 48 | text-align: right; 49 | border: none; 50 | padding: 1rem; 51 | 52 | &:before { 53 | content: attr(data-th); 54 | font-weight: 600; 55 | display: inline-block; 56 | margin-right: 1rem; 57 | float: left; 58 | } 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/components/_tooltip.scss: -------------------------------------------------------------------------------- 1 | .tooltip { 2 | position: relative; 3 | z-index: 999; 4 | color: $brand-primary; 5 | white-space: nowrap; 6 | font-size: 1.4rem; 7 | height: 20px; 8 | display: inline-block; 9 | 10 | &:after { 11 | content: attr(data-title); 12 | position: absolute; 13 | top: 50%; 14 | left: 100%; 15 | transform: translate(0%, -50%); 16 | background: black; 17 | border-radius: 2px; 18 | color: white; 19 | font-style: normal; 20 | text-align: left; 21 | opacity: 0; 22 | pointer-events: none; 23 | font-size: 1.4rem; 24 | padding: 10px 15px; 25 | font-weight: 400; 26 | width: auto; 27 | min-width: 140px; 28 | max-width: 350px; 29 | white-space: normal; 30 | transition: opacity .2s ease-in-out; 31 | } 32 | 33 | &:before { 34 | content: ''; 35 | display: block; 36 | position: absolute; 37 | opacity: 0; 38 | top: 50%; 39 | left: 100%; 40 | transform: translate(-100%,-50%); 41 | border: 7px solid transparent; 42 | border-right-color: black; 43 | transition: opacity .2s ease-in-out; 44 | } 45 | 46 | &:hover:after, 47 | &:hover:before { 48 | opacity: 1; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/layout/_container.scss: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 100%; 3 | max-width: 970px; 4 | margin: auto; 5 | 6 | @include respond-to('sm-down') { 7 | padding: 0 1rem; 8 | } 9 | } 10 | 11 | .container-small { 12 | width: 100%; 13 | max-width: 960px; 14 | margin: auto; 15 | 16 | @include respond-to('sm-down') { 17 | padding: 1rem; 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/layout/_footer.scss: -------------------------------------------------------------------------------- 1 | footer { 2 | @extend %clearfix; 3 | display: block; 4 | background-color: $brand-navy; 5 | padding-top: 4rem; 6 | padding-bottom: 4rem; 7 | color: #fff; 8 | position: relative; 9 | border-top: 1px solid lighten($brand-navy, 10%); 10 | 11 | .brand { 12 | max-width: 20rem; 13 | } 14 | 15 | .container { 16 | display: flex; 17 | align-items: baseline; 18 | 19 | @include respond-to(xs) { 20 | flex-direction: column; 21 | } 22 | } 23 | } 24 | 25 | .footer-left, 26 | .footer-right { 27 | flex: 1; 28 | } 29 | 30 | .footer-right { 31 | text-align: right; 32 | 33 | @include respond-to(xs) { 34 | text-align: left; 35 | } 36 | } 37 | 38 | .stripe { 39 | background-image: url(../assets/images/web-racing-stripe.png); 40 | background-repeat: no-repeat; 41 | background-size: 50%; 42 | background-position: right; 43 | width: 100%; 44 | height: 10px; 45 | position: absolute; 46 | top: 0; 47 | right: 0; 48 | } 49 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/layout/_header.scss: -------------------------------------------------------------------------------- 1 | header { 2 | padding: 5rem 0; 3 | position: relative; 4 | z-index: 1; 5 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/layout/_navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | @extend %clearfix; 3 | position: relative; 4 | padding: 0.5rem 1rem; 5 | background-color: $brand-navy; 6 | z-index: 11; 7 | 8 | .container { 9 | max-width: 100%; 10 | display: flex; 11 | flex-flow: row nowrap; 12 | justify-content: space-between; 13 | align-items: center; 14 | 15 | @include respond-to("sm-down") { 16 | flex-direction: column; 17 | } 18 | } 19 | 20 | nav { 21 | display: inline-block; 22 | vertical-align: middle; 23 | 24 | a { 25 | padding: 18px 20px; 26 | color: #fff; 27 | text-transform: uppercase; 28 | 29 | &:hover { 30 | color: $link-color; 31 | } 32 | 33 | &.active { 34 | color: $link-color; 35 | box-shadow: 0 2px 0 0 $link-color; 36 | } 37 | } 38 | } 39 | } 40 | 41 | .navbar-left { 42 | flex: none; 43 | } 44 | 45 | .navbar-right { 46 | flex: none; 47 | } 48 | 49 | .pfb-logo-small { 50 | width: auto; 51 | height: 4rem; 52 | } 53 | 54 | .brand { 55 | display: flex; 56 | flex-direction: row; 57 | 58 | @include respond-to("sm-down") { 59 | flex-direction: column; 60 | margin-top: 2.4rem; 61 | margin-bottom: 0.8rem; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/layout/_section.scss: -------------------------------------------------------------------------------- 1 | section { 2 | @extend %clearfix; 3 | display: block; 4 | position: relative; 5 | padding-top: 6rem; 6 | padding-bottom: 6rem; 7 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/pages/_compare.scss: -------------------------------------------------------------------------------- 1 | .comparisons { 2 | background-color: $shade-light; 3 | 4 | .card { 5 | margin-top: 2rem; 6 | } 7 | 8 | .compare-remove { 9 | position: absolute; 10 | right: 10px; 11 | top: 10px; 12 | font-size: 22px; 13 | color: #000; 14 | opacity: .5; 15 | transition: .2s ease-in-out opacity; 16 | 17 | &:hover { 18 | opacity: 1; 19 | } 20 | } 21 | } -------------------------------------------------------------------------------- /src/angularjs/src/styles/pages/_location.scss: -------------------------------------------------------------------------------- 1 | .location-overview { 2 | 3 | .dropdown { 4 | display: inline; 5 | } 6 | 7 | .metric-details { 8 | padding: 0 2rem; 9 | background-color: $shade-light; 10 | border-bottom: 1px solid darken($shade-light, 5%); 11 | position: relative; 12 | z-index: 99; 13 | 14 | .row { 15 | margin: 0; 16 | } 17 | 18 | .network-score { 19 | margin: 0; 20 | 21 | .h3 { 22 | margin-left: 1rem; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/utils/_mixins.scss: -------------------------------------------------------------------------------- 1 | /* * * * 2 | * Responsive Breakpoint Manager 3 | * requires $breakpoints from _variables.scss > $breakpoints 4 | * Useage: @include respond-to('small') {...} 5 | * * * */ 6 | @mixin respond-to($breakpoint) { 7 | // If the key exists in the map 8 | @if map-has-key($breakpoints, $breakpoint) { 9 | // Prints a media query based on the value 10 | @media #{inspect(map-get($breakpoints, $breakpoint))} { 11 | @content; 12 | } 13 | } 14 | 15 | // If the key doesn't exist in the map 16 | @else { 17 | @warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. " 18 | + "Available breakpoints are: #{map-keys($breakpoints)}."; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/angularjs/src/styles/utils/_variables.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * LMC Variables 3 | * If overridng bootstrap please place inside _bs-variables.scss 4 | */ 5 | 6 | // Brand Colors: 7 | $brand-primary: #00a1e1; // blue 8 | $brand-secondary: #e31d1a; // red 9 | $brand-navy: #002c40; 10 | 11 | // Base Colors: 12 | $text-base: #000000; 13 | $shade-light: #f3f3f3; 14 | $shade-normal: #bcbcbc; 15 | $shade-dark: #9da0a7; 16 | $link-color: $brand-primary; 17 | $white: #fff; 18 | 19 | // Action Colors: 20 | $warning: #E69348; // orange 21 | $danger: #F98094; // red 22 | $success: #21ce90; // red 23 | 24 | // Border Radius 25 | $border-radius-base: 2px; 26 | 27 | 28 | /* * * * 29 | * Screen Sizes 30 | * Used in _mixins.scss > @mixin respond-to($breakpoint) 31 | * * * */ 32 | $breakpoints: ( 33 | 34 | 'xxs': (max-width: 480px), 35 | 36 | 'xs': (max-width: 767px), 37 | 38 | 'sm': "(min-width: 768px) and (max-width: 991px)", 39 | 'sm-up': (min-width: 768px), 40 | 'sm-down': (max-width: 991px), 41 | 42 | 'md': "(min-width: 992px) and (max-width: 1180px)", 43 | 'md-up': (min-width: 992px), 44 | 'md-down': (max-width: 1180px), 45 | 46 | 'lg': (min-width: 1181px), 47 | ); 48 | 49 | 50 | /* * * * 51 | * Columns 52 | * * * */ 53 | $column-count: 12; 54 | $column-padding: 1rem; 55 | -------------------------------------------------------------------------------- /src/django/Dockerfile: -------------------------------------------------------------------------------- 1 | # Note: the Django and psycopg2 versions are repeated in requirements.txt for the benefit 2 | # of the analysis container. The base container and requirements file versions should be kept 3 | # in sync. 4 | FROM python:3.10-slim-bullseye 5 | 6 | WORKDIR /usr/src 7 | COPY requirements.txt /tmp/ 8 | 9 | RUN set -ex \ 10 | && buildDeps=" \ 11 | build-essential \ 12 | libpq-dev \ 13 | " \ 14 | && deps=" \ 15 | gdal-bin \ 16 | libgdal-dev \ 17 | gettext \ 18 | postgresql-client-13 \ 19 | " \ 20 | && apt-get update && apt-get install -y $buildDeps $deps --no-install-recommends \ 21 | && pip install --no-cache-dir -r /tmp/requirements.txt \ 22 | && apt-get purge -y --auto-remove $buildDeps \ 23 | && rm -rf /tmp/requirements.txt /var/lib/apt/lists/* 24 | 25 | COPY . /usr/src 26 | 27 | EXPOSE 9202 28 | 29 | ENTRYPOINT ["/usr/local/bin/gunicorn"] 30 | -------------------------------------------------------------------------------- /src/django/gunicorn.conf.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | bind = ":9202" 4 | accesslog = "-" 5 | errorlog = "-" 6 | workers = 3 7 | worker_class = 'gevent' 8 | loglevel = "Info" 9 | 10 | ENVIRONMENT = os.getenv("DJANGO_ENV", "dev") 11 | 12 | if ENVIRONMENT == "development": 13 | reload = True 14 | else: 15 | preload = True 16 | 17 | wsgi_app = "pfb_network_connectivity.wsgi" 18 | -------------------------------------------------------------------------------- /src/django/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import os 3 | import sys 4 | 5 | if __name__ == "__main__": 6 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pfb_network_connectivity.settings") 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError: 10 | # The above import may fail for some other reason. Ensure that the 11 | # issue is really that Django is missing to avoid masking other 12 | # exceptions on Python 2. 13 | try: 14 | import django 15 | except ImportError: 16 | raise ImportError( 17 | "Couldn't import Django. Are you sure it's installed and " 18 | "available on your PYTHONPATH environment variable? Did you " 19 | "forget to activate a virtual environment?" 20 | ) 21 | raise 22 | execute_from_command_line(sys.argv) 23 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/__init__.py -------------------------------------------------------------------------------- /src/django/pfb_analysis/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class PfbAnalysisConfig(AppConfig): 7 | name = 'pfb_analysis' 8 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/functions.py: -------------------------------------------------------------------------------- 1 | from django.db.models.expressions import Func 2 | 3 | 4 | class ObjectAtPath(Func): 5 | """ Database function to extract an object or value from a JSONField """ 6 | function = '#>' 7 | template = "%(expressions)s%(function)s'{%(path)s}'" 8 | arity = 1 9 | 10 | def __init__(self, expression, path, **extra): 11 | # if path is a list, convert it to a comma separated string 12 | if isinstance(path, (list, tuple)): 13 | path = ','.join(path) 14 | super(ObjectAtPath, self).__init__(expression, path=path, **extra) 15 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/management/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/management/__init__.py -------------------------------------------------------------------------------- /src/django/pfb_analysis/management/commands/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/management/commands/__init__.py -------------------------------------------------------------------------------- /src/django/pfb_analysis/management/commands/analysis_batch_cancel.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from pfb_analysis.models import AnalysisBatch 4 | 5 | 6 | class Command(BaseCommand): 7 | help = """ Cancel all jobs in an AnalysisBatch """ 8 | 9 | def add_arguments(self, parser): 10 | parser.add_argument('batch_uuid', type=str) 11 | 12 | def handle(self, *args, **options): 13 | batch_uuid = options['batch_uuid'] 14 | 15 | batch = AnalysisBatch.objects.get(uuid=batch_uuid) 16 | self.stdout.write('Cancelling {}'.format(batch)) 17 | batch.cancel() 18 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/management/commands/run_analysis_job.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from pfb_analysis.models import AnalysisJob, Neighborhood 4 | from users.models import PFBUser 5 | 6 | 7 | class Command(BaseCommand): 8 | help = """ Create and run an AnalysisJob for the given neighborhood 9 | 10 | The neighborhood name must match an existing Neighborhood for the user's organization. 11 | 12 | """ 13 | 14 | def add_arguments(self, parser): 15 | # Positional arguments 16 | parser.add_argument('neighborhood') 17 | parser.add_argument('--user', default=None, type=str) 18 | 19 | def handle(self, *args, **options): 20 | if options['user'] is not None: 21 | user = PFBUser.objects.get(email=options['user']) 22 | else: 23 | user = PFBUser.objects.get_root_user() 24 | neighborhood = Neighborhood.objects.get(name=options['neighborhood'], 25 | organization=user.organization) 26 | job = AnalysisJob.objects.create(neighborhood=neighborhood, 27 | created_by=user, 28 | modified_by=user) 29 | job.run() 30 | self.stdout.write('Started job {} for {}'.format(job.batch_job_id, neighborhood.name)) 31 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/management/commands/update_status.py: -------------------------------------------------------------------------------- 1 | from django.core.management.base import BaseCommand 2 | 3 | from pfb_analysis.models import AnalysisJob 4 | 5 | 6 | class Command(BaseCommand): 7 | help = "Update status during an analysis job" 8 | 9 | def add_arguments(self, parser): 10 | # Positional arguments 11 | parser.add_argument('job_id') 12 | parser.add_argument('status') 13 | parser.add_argument('step') 14 | parser.add_argument('message', nargs='?', default='') 15 | 16 | def handle(self, *args, **options): 17 | try: 18 | job = AnalysisJob.objects.get(pk=options['job_id']) 19 | except (AnalysisJob.DoesNotExist, ValueError, KeyError): 20 | print('WARNING: Tried to update status for invalid job {job_id} ' 21 | '(to {status} {step})'.format(**options)) 22 | else: 23 | job.update_status(options['status'], step=options['step'], message=options['message']) 24 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0002_analysisjob_batch_job_id.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-03 16:19 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0001_initial'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='analysisjob', 17 | name='batch_job_id', 18 | field=models.CharField(blank=True, max_length=256, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0003_merge_20170307_1930.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-07 19:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0002_auto_20170307_1640'), 12 | ('pfb_analysis', '0002_analysisjob_batch_job_id'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0004_analysisjob_osm_extract_url.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-21 14:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0003_merge_20170307_1930'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='analysisjob', 17 | name='osm_extract_url', 18 | field=models.URLField(blank=True, help_text='Load OSM data for this neighborhood from a URL rather than pulling from Overpass API. The url must have a .osm file extension and may optionally be compressed via zip/bzip/gz, e.g. http://a.com/foo.osm or http://a.com/foo.osm.bz2', max_length=2048, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0005_remove_analysisjob_status.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-17 20:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0004_analysisjobstatusupdate'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='analysisjob', 17 | name='status', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0006_merge_20170321_1656.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-21 16:56 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0004_auto_20170317_2018'), 12 | ('pfb_analysis', '0005_remove_analysisjob_status'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0006_merge_20170322_1647.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-22 16:47 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0004_analysisjob_osm_extract_url'), 12 | ('pfb_analysis', '0005_remove_analysisjob_status'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0007_auto_20170322_1309.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-22 13:09 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0006_merge_20170321_1656'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='analysisjobstatusupdate', 17 | name='status', 18 | field=models.CharField(choices=[('CREATED', 'Created'), ('QUEUED', 'Queued'), ('IMPORTING', 'Importing Data'), ('BUILDING', 'Building Network Graph'), ('CONNECTIVITY', 'Calculating Connectivity'), ('METRICS', 'Calculating Graph Metrics'), ('EXPORTING', 'Exporting Results'), (('CANCELLED',), 'Cancelled'), ('COMPLETE', 'Complete'), ('ERROR', 'Error')], max_length=12), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0008_merge_20170323_1200.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-23 12:00 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0007_auto_20170322_1309'), 12 | ('pfb_analysis', '0006_merge_20170322_1647'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0009_auto_20170323_1535.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-23 15:35 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0008_merge_20170323_1200'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='analysisjobstatusupdate', 17 | name='status', 18 | field=models.CharField(choices=[('CREATED', 'Created'), ('QUEUED', 'Queued'), ('IMPORTING', 'Importing Data'), ('BUILDING', 'Building Network Graph'), ('CONNECTIVITY', 'Calculating Connectivity'), ('METRICS', 'Calculating Graph Metrics'), ('EXPORTING', 'Exporting Results'), ('CANCELLED', 'Cancelled'), ('COMPLETE', 'Complete'), ('ERROR', 'Error')], max_length=12), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0010_analysisjob_overall_scores.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-24 13:35 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.postgres.fields.jsonb 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0009_auto_20170323_1535'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='analysisjob', 18 | name='overall_scores', 19 | field=django.contrib.postgres.fields.jsonb.JSONField(db_index=True, default=dict), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0011_analysisjob_census_block_count.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-24 19:58 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0010_analysisjob_overall_scores'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='analysisjob', 17 | name='census_block_count', 18 | field=models.PositiveIntegerField(blank=True, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0012_analysisscoremetadata.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-03-30 20:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0011_analysisjob_census_block_count'), 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='AnalysisScoreMetadata', 17 | fields=[ 18 | ('name', models.CharField(max_length=128, primary_key=True, serialize=False)), 19 | ('label', models.CharField(blank=True, help_text='Short descriptive name', max_length=256, null=True)), 20 | ('category', models.CharField(blank=True, help_text='Used to group scores with the same category together', max_length=128, null=True)), 21 | ('description', models.CharField(blank=True, help_text='Long description of the metric', max_length=1024, null=True)), 22 | ], 23 | options={ 24 | 'ordering': ('name',), 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0013_auto_20170407_0106.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-07 01:06 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0012_analysisscoremetadata'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='analysisjobstatusupdate', 17 | name='status', 18 | field=models.CharField(choices=[('CREATED', 'Created'), ('QUEUED', 'Queued'), ('IMPORTING', 'Importing Data'), ('BUILDING', 'Building Network Graph'), ('CONNECTIVITY', 'Calculating Connectivity'), ('METRICS', 'Calculating Graph Metrics'), ('EXPORTING', 'Exporting Results'), ('EXPORTED', 'Analysis Finished'), ('TILING', 'Generating Map Tiles'), ('COMPLETE', 'Complete'), ('CANCELLED', 'Cancelled'), ('ERROR', 'Error')], max_length=12), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0014_auto_20170412_1611.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-12 16:11 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import pfb_analysis.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0013_auto_20170407_0106'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='analysisjob', 18 | name='_analysis_job_name', 19 | field=models.CharField(default='', max_length=50), 20 | ), 21 | migrations.AddField( 22 | model_name='analysisjob', 23 | name='_tilemaker_job_name', 24 | field=models.CharField(default='', max_length=50), 25 | ), 26 | migrations.AddField( 27 | model_name='analysisjob', 28 | name='analysis_job_definition', 29 | field=models.CharField(default=pfb_analysis.models.generate_analysis_job_def, max_length=50), 30 | ), 31 | migrations.AddField( 32 | model_name='analysisjob', 33 | name='tilemaker_job_definition', 34 | # Formerly had 'default=pfb_analysis.models.generate_tilemaker_job_def', but that crashes 35 | field=models.CharField(default='', max_length=50), 36 | ), 37 | ] 38 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0014_neighborhood_geom.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-11 15:15 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0013_auto_20170407_0106'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='neighborhood', 18 | name='geom', 19 | field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, null=True, srid=4326), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0016_merge_20170412_1804.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-12 18:04 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0014_auto_20170412_1611'), 12 | ('pfb_analysis', '0015_add_neighborhood_geoms'), 13 | ] 14 | 15 | operations = [ 16 | ] 17 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0017_neighborhood_geom_pt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-13 14:59 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0016_merge_20170412_1804'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='neighborhood', 18 | name='geom_pt', 19 | field=django.contrib.gis.db.models.fields.PointField(blank=True, null=True, srid=4326), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0018_add_neighborhood_geom_pt.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-13 14:59 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations 7 | 8 | 9 | def set_neighborhood_geom_pt(apps, schema_editor): 10 | Neighborhood = apps.get_model("pfb_analysis", "Neighborhood") 11 | for n in Neighborhood.objects.all(): 12 | n.geom_pt = n.geom.centroid 13 | n.save() 14 | 15 | 16 | class Migration(migrations.Migration): 17 | 18 | dependencies = [ 19 | ('pfb_analysis', '0017_neighborhood_geom_pt'), 20 | ] 21 | 22 | operations = [ 23 | migrations.RunPython(set_neighborhood_geom_pt) 24 | ] 25 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0019_auto_20170421_1746.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-21 17:46 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import pfb_analysis.models 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0018_add_neighborhood_geom_pt'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='neighborhood', 18 | name='boundary_file', 19 | field=models.FileField(help_text='A zipped shapefile boundary to run the bike network analysis on', max_length=1024, upload_to=pfb_analysis.models.get_neighborhood_file_upload_path), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0020_neighborhood_geom_simple.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-04-26 14:58 3 | from __future__ import unicode_literals 4 | 5 | import django.contrib.gis.db.models.fields 6 | from django.db import migrations 7 | 8 | from pfb_analysis.models import simplify_geom 9 | 10 | 11 | def add_neighborhood_simplified_geoms(apps, schema_editor): 12 | Neighborhood = apps.get_model("pfb_analysis", "Neighborhood") 13 | for n in Neighborhood.objects.all(): 14 | n.geom_simple = simplify_geom(n.geom) 15 | n.save() 16 | 17 | 18 | class Migration(migrations.Migration): 19 | 20 | dependencies = [ 21 | ('pfb_analysis', '0019_auto_20170421_1746'), 22 | ] 23 | 24 | operations = [ 25 | migrations.AddField( 26 | model_name='neighborhood', 27 | name='geom_simple', 28 | field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, 29 | null=True, 30 | srid=4326), 31 | ), 32 | migrations.RunPython(add_neighborhood_simplified_geoms, 33 | reverse_code=migrations.RunPython.noop) 34 | ] 35 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0021_neighborhood_visibility.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-05-09 14:52 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0020_neighborhood_geom_simple'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='neighborhood', 17 | name='visibility', 18 | field=models.CharField(choices=[('public', 'Public'), ('private', 'Private')], default='public', max_length=10), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0024_auto_20170509_2121.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-09 21:21 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0023_auto_20170509_2110'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RenameField( 16 | model_name='analysisjob', 17 | old_name='last_status', 18 | new_name='status', 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0025_auto_20170511_1244.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-05-11 12:44 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0024_auto_20170509_2121'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='neighborhood', 18 | name='last_job', 19 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='last_job_neighborhood', to='pfb_analysis.AnalysisJob'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0026_auto_20170517_1531.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-17 15:31 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0004_auto_20170509_1559'), 12 | ('pfb_analysis', '0025_auto_20170511_1244'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='neighborhood', 18 | name='label', 19 | field=models.CharField(help_text='Human-readable label for neighborhood, should not include State', max_length=256), 20 | ), 21 | migrations.AlterUniqueTogether( 22 | name='neighborhood', 23 | unique_together=set([('name', 'state_abbrev', 'organization')]), 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0027_remove_states_from_labels.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-18 13:19 3 | from __future__ import unicode_literals 4 | import re 5 | from django.utils.text import slugify 6 | 7 | from django.db import migrations 8 | 9 | 10 | def remove_states_from_labels(apps, schema_editor): 11 | Neighborhood = apps.get_model("pfb_analysis", "Neighborhood") 12 | for n in Neighborhood.objects.all(): 13 | # Remove state abbreviation from label 14 | n.label = re.sub(r', *[A-Z]{2} *$', '', n.label) 15 | # Set name to new slugified label 16 | n.name = slugify(n.label) 17 | n.save() 18 | 19 | 20 | class Migration(migrations.Migration): 21 | 22 | dependencies = [ 23 | ('pfb_analysis', '0026_auto_20170517_1531'), 24 | ] 25 | 26 | operations = [ 27 | migrations.RunPython(remove_states_from_labels, reverse_code=migrations.RunPython.noop), 28 | ] 29 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0028_analysisscoremetadata_priority.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-22 13:20 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0027_remove_states_from_labels'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='analysisscoremetadata', 17 | name='priority', 18 | field=models.PositiveSmallIntegerField(blank=True, help_text='Determines sort order in response, lower numbers sort first', null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0029_auto_20170802_1425.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-08-02 14:25 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0028_analysisscoremetadata_priority'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='neighborhood', 17 | name='visibility', 18 | field=models.CharField(choices=[('public', 'Public'), ('private', 'Private'), ('hidden', 'Hidden')], default='public', max_length=10), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0030_auto_20180419_1342.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.11 on 2018-04-19 13:42 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0029_auto_20170802_1425'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='analysisjob', 17 | name='osm_extract_url', 18 | field=models.URLField(blank=True, help_text='Load OSM data for this neighborhood from a URL rather than pulling from Goefabrik extracts. The url must be to an uncompressed OSM file (with .osm extension) or a compressed OSM file (with .osm.zip, .osm.gzip, .osm.bz2, or .osm.pbf extension). e.g. http://a.com/foo.osm or http://a.com/foo.osm.bz2', max_length=2048, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0032_neighborhood_country.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2018-12-05 16:30 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import django_countries.fields 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('pfb_analysis', '0031_censusblocksresults_neighborhoodwaysresults'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='neighborhood', 18 | name='country', 19 | field=django_countries.fields.CountryField(default='US', help_text='The country of the uploaded neighborhood', max_length=2), 20 | preserve_default=False, 21 | ), 22 | ] 23 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0033_auto_20181205_1913.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2018-12-05 19:13 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | import django_countries.fields 7 | import localflavor.us.models 8 | 9 | 10 | class Migration(migrations.Migration): 11 | 12 | dependencies = [ 13 | ('users', '0004_auto_20170509_1559'), 14 | ('pfb_analysis', '0032_neighborhood_country'), 15 | ] 16 | 17 | operations = [ 18 | migrations.AlterField( 19 | model_name='neighborhood', 20 | name='country', 21 | field=django_countries.fields.CountryField(default='US', help_text='The country of the uploaded neighborhood', max_length=2), 22 | ), 23 | migrations.AlterField( 24 | model_name='neighborhood', 25 | name='state_abbrev', 26 | field=localflavor.us.models.USStateField(blank=True, help_text='The state of the uploaded neighborhood, if in the US', max_length=2, null=True), 27 | ), 28 | migrations.AlterUniqueTogether( 29 | name='neighborhood', 30 | unique_together=set([('name', 'country', 'state_abbrev', 'organization')]), 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0035_auto_20190124_1853.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2019-01-24 18:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0034_add_analysislocaluploadtask_model'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='analysislocaluploadtask', 17 | name='status', 18 | field=models.CharField(choices=[('CREATED', 'Created'), ('QUEUED', 'Queued'), ('IMPORTING', 'Importing'), ('COMPLETE', 'Complete'), ('ERROR', 'Error')], default='CREATED', max_length=16), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0036_neighborhood_city_fips.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2019-01-31 19:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0035_auto_20190124_1853'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AddField( 16 | model_name='neighborhood', 17 | name='city_fips', 18 | field=models.CharField(blank=True, default='', max_length=7), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0039_alter_neighborhood_state_abbrev_field.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.11.13 on 2019-04-06 02:46 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('pfb_analysis', '0038_move_territories_to_us'), 12 | ] 13 | 14 | operations = [ 15 | migrations.AlterField( 16 | model_name='neighborhood', 17 | name='state_abbrev', 18 | field=models.CharField(blank=True, help_text='The state/province of the uploaded neighborhood', max_length=10, null=True), 19 | ), 20 | ] 21 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0040_neighborhood_analysis_job_ondelete.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.5 on 2019-11-11 16:12 2 | 3 | from django.db import migrations, models 4 | import django.db.models.deletion 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('pfb_analysis', '0039_alter_neighborhood_state_abbrev_field'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AlterField( 15 | model_name='neighborhood', 16 | name='last_job', 17 | field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='last_job_neighborhood', to='pfb_analysis.AnalysisJob'), 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0041_auto_20201006_2320.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.10 on 2020-10-06 23:20 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0040_neighborhood_analysis_job_ondelete'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='analysisjob', 15 | name='default_speed_limit', 16 | field=models.PositiveIntegerField(blank=True, null=True), 17 | ), 18 | migrations.AddField( 19 | model_name='analysisjob', 20 | name='speed_limit_src', 21 | field=models.CharField(blank=True, choices=[('State', 'State'), ('City', 'City')], max_length=20, null=True), 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0042_analysisjob_max_trip_distance.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.2.16 on 2022-04-01 16:58 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0041_auto_20201006_2320'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='analysisjob', 15 | name='max_trip_distance', 16 | field=models.PositiveIntegerField(blank=True, default=2680, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0043_update_jsonfield_django_32.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-06-09 17:21 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0042_analysisjob_max_trip_distance'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AlterField( 14 | model_name='analysisjob', 15 | name='overall_scores', 16 | field=models.JSONField(db_index=True, default=dict), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0044_analysisjob_population_url.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-06-28 18:30 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0043_update_jsonfield_django_32'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='analysisjob', 15 | name='population_url', 16 | field=models.URLField(blank=True, max_length=2048, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0045_analysisjob_skip_import_jobs.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-07-01 20:04 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0044_analysisjob_population_url'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='analysisjob', 15 | name='skip_import_jobs', 16 | field=models.BooleanField(default=False), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0046_analysisjob_jobs_url_field.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-07-01 20:53 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0045_analysisjob_skip_import_jobs'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='analysisjob', 15 | name='jobs_url', 16 | field=models.URLField(blank=True, max_length=2048, null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0047_neighborhood_speed_limit.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-08-22 16:37 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | dependencies = [ 9 | ('pfb_analysis', '0046_analysisjob_jobs_url_field'), 10 | ] 11 | 12 | operations = [ 13 | migrations.AddField( 14 | model_name='neighborhood', 15 | name='speed_limit', 16 | field=models.IntegerField(blank=True, help_text='The default residential speed limit, in MPH', null=True), 17 | ), 18 | ] 19 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0048_crash.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-12-01 20:27 2 | 3 | import django.contrib.gis.db.models.fields 4 | from django.db import migrations, models 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('pfb_analysis', '0047_neighborhood_speed_limit'), 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Crash', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('fatality_count', models.IntegerField()), 19 | ('fatality_type', models.CharField(choices=[('ACTIVE', 'Other Active Transport'), ('BIKE', 'Bike'), ('MOTOR_VEHICLE', 'Motor Vehicle')], max_length=16)), 20 | ('geom_pt', django.contrib.gis.db.models.fields.PointField(srid=4326, geography=True)), 21 | ('year', models.IntegerField()), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/0049_neighborhood_geog.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 3.2.13 on 2022-12-05 20:50 2 | 3 | import django.contrib.gis.db.models.fields 4 | from django.db import migrations 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | dependencies = [ 10 | ('pfb_analysis', '0048_crash'), 11 | ] 12 | 13 | operations = [ 14 | migrations.AddField( 15 | model_name='neighborhood', 16 | name='geog', 17 | field=django.contrib.gis.db.models.fields.MultiPolygonField(blank=True, geography=True, null=True, srid=4326), 18 | ), 19 | migrations.RunSQL( 20 | "UPDATE pfb_analysis_neighborhood SET geog = geom;", 21 | migrations.RunSQL.noop 22 | ) 23 | ] 24 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/migrations/__init__.py -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/__init__.py -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.cpg: -------------------------------------------------------------------------------- 1 | ISO-8859-1 -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.dbf: -------------------------------------------------------------------------------- 1 | uA FIDN 0 -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.shp -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/ann-arbor-mi/ann-arbor-mi.shx -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.dbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.dbf -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.qpj: -------------------------------------------------------------------------------- 1 | GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]] 2 | -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.shp -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.shx -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/batch_create_shapefile/PFB_BigJumpCities_1.zip -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/birmingham/birmingham.cpg: -------------------------------------------------------------------------------- 1 | ISO-8859-1 -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/birmingham/birmingham.dbf: -------------------------------------------------------------------------------- 1 | uA FIDN 0 -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/birmingham/birmingham.prj: -------------------------------------------------------------------------------- 1 | GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["Degree",0.017453292519943295]] -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/birmingham/birmingham.shp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/birmingham/birmingham.shp -------------------------------------------------------------------------------- /src/django/pfb_analysis/tests/data/birmingham/birmingham.shx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_analysis/tests/data/birmingham/birmingham.shx -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/__init__.py: -------------------------------------------------------------------------------- 1 | from __future__ import absolute_import, unicode_literals 2 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-02-21 16:50 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | initial = True 11 | 12 | dependencies = [ 13 | ] 14 | 15 | operations = [ 16 | migrations.CreateModel( 17 | name='Neighborhood', 18 | fields=[ 19 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 20 | ('name', models.TextField()), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/migrations/0002_delete_neighborhood.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-02-24 16:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0003_auto_20170224_1639'), 12 | ('pfb_network_connectivity', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.DeleteModel( 17 | name='Neighborhood', 18 | ), 19 | ] 20 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/pfb_network_connectivity/migrations/__init__.py -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/pagination.py: -------------------------------------------------------------------------------- 1 | from rest_framework.pagination import LimitOffsetPagination 2 | 3 | 4 | class OptionalLimitOffsetPagination(LimitOffsetPagination): 5 | """ 6 | Allow client to request all by setting limit parameter to 'all' 7 | 8 | Inspired by https://github.com/azavea/ashlar/blob/develop/ashlar/pagination.py 9 | """ 10 | def paginate_queryset(self, queryset, request, view=None): 11 | self.limit = self.get_limit(request) 12 | # set the limit to one more than the queryset count 13 | if self.limit == 'all': 14 | self.limit = self.get_count(queryset) + 1 15 | return super(OptionalLimitOffsetPagination, self).paginate_queryset(queryset, request, view) 16 | 17 | def get_limit(self, request): 18 | # If the limit is already set as an integer (e.g. because we're in the 19 | # super.paginate_queryset call), just return it 20 | if type(getattr(self, 'limit', None)) == int: 21 | return self.limit 22 | if self.limit_query_param and request.query_params.get(self.limit_query_param) == 'all': 23 | return 'all' 24 | return super(OptionalLimitOffsetPagination, self).get_limit(request) 25 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import requests 4 | 5 | 6 | def download_file(url, local_filename=None): 7 | if not local_filename: 8 | local_filename = os.path.join('.', url.split('/')[-1]) 9 | r = requests.get(url, stream=True) 10 | with open(local_filename, 'wb') as f: 11 | for chunk in r.iter_content(chunk_size=1024): 12 | if chunk: # filter out keep-alive new chunks 13 | f.write(chunk) 14 | return local_filename 15 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /src/django/pfb_network_connectivity/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for pfb_network_connectivity project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pfb_network_connectivity.settings") 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /src/django/requirements.txt: -------------------------------------------------------------------------------- 1 | # Django and psycopg2 are included in the django base container, but the analysis 2 | # container also needs them, and uses this same requirements file. 3 | # Which means the versions here will be the ones that get installed, in both places. 4 | # Ideally these should be kept in sync with the versions in the base container 5 | # (https://github.com/azavea/docker-django/blob/master/3.2/requirements.txt) to avoid 6 | # downgrading when building the django container. 7 | Django==4.2.17 8 | psycopg2-binary==2.9.3 9 | 10 | boto3==1.23.10 11 | django-amazon-ses==4.0.0 12 | # Note: django-countries 7.3 changes how the field's filters work, so we want to stay on 7.2.1 13 | django-countries==7.2.1 14 | django-extensions==3.1.5 15 | django-filter==2.4.0 16 | django-q==1.3.9 17 | django-storages==1.12.3 18 | django-watchman==1.3.0 19 | djangorestframework==3.15.2 20 | fiona==1.8.21 21 | future==0.18.2 22 | gevent==24.11.1 23 | gunicorn==23.0.0 24 | pycountry==22.3.5 25 | pyuca==1.2 26 | requests==2.27.1 27 | us==2.0.2 28 | 29 | # This been removed from application code but was used in old migrations, so it can't be removed 30 | # unless those migrations are refactored to not import it. 31 | django-localflavor==4.0 32 | -------------------------------------------------------------------------------- /src/django/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | 3 | ignore = D100,D101,D102,D103,D104 4 | max-line-length = 100 5 | exclude = migrations,*_migrations,manage.py 6 | max-complexity = 15 7 | -------------------------------------------------------------------------------- /src/django/users/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/users/__init__.py -------------------------------------------------------------------------------- /src/django/users/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /src/django/users/apps.py: -------------------------------------------------------------------------------- 1 | from __future__ import unicode_literals 2 | 3 | from django.apps import AppConfig 4 | 5 | 6 | class UsersConfig(AppConfig): 7 | name = 'users' 8 | -------------------------------------------------------------------------------- /src/django/users/emails.py: -------------------------------------------------------------------------------- 1 | """Python file holding emails to be sent""" 2 | 3 | password_reset_txt = """ 4 | Hello {username}, 5 | 6 | Someone has requested a password reset link. 7 | 8 | If this was you, please reset your password at 9 | 10 | {url} 11 | 12 | If not, please disregard this e-mail. 13 | 14 | Thank you! 15 | """ 16 | 17 | user_registration_txt = """ 18 | Hello {user_firstname}, 19 | 20 | {created_by_name} from the {created_by_organization} organization has created a user ({username}) 21 | for you in the {created_organization} organization. 22 | 23 | To complete registration please set your password at 24 | 25 | {url} 26 | 27 | Thank you! 28 | """ 29 | -------------------------------------------------------------------------------- /src/django/users/migrations/0002_auto_20170221_1653.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-02-21 16:53 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | import django.db.models.deletion 7 | 8 | 9 | class Migration(migrations.Migration): 10 | 11 | dependencies = [ 12 | ('users', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AlterField( 17 | model_name='pfbuser', 18 | name='organization', 19 | field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='users.Organization'), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /src/django/users/migrations/0003_auto_20170224_1639.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.3 on 2017-02-24 16:39 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | ('users', '0002_auto_20170221_1653'), 12 | ] 13 | 14 | operations = [ 15 | migrations.RemoveField( 16 | model_name='organization', 17 | name='neighborhood', 18 | ), 19 | migrations.AlterField( 20 | model_name='organization', 21 | name='org_type', 22 | field=models.CharField(choices=[('ADMIN', 'PFB Administrator Organization'), ('SUBSCRIBER', 'Subscriber')], max_length=10), 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /src/django/users/migrations/0004_auto_20170509_1559.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Generated by Django 1.10.7 on 2017-05-09 15:59 3 | from __future__ import unicode_literals 4 | 5 | from django.db import migrations, models 6 | 7 | 8 | def makeUsersViewers(apps, schema_editor): 9 | """Make all non-admin users viewers.""" 10 | PFBUser = Neighborhood = apps.get_model("users", "PFBUser") 11 | for user in PFBUser.objects.filter(role__in=('EDITOR', 'UPLOADER')): 12 | user.role = 'VIEWER' 13 | user.save() 14 | 15 | 16 | class Migration(migrations.Migration): 17 | 18 | dependencies = [ 19 | ('users', '0003_auto_20170224_1639'), 20 | ] 21 | 22 | operations = [ 23 | migrations.RunPython(makeUsersViewers), 24 | migrations.AlterField( 25 | model_name='pfbuser', 26 | name='role', 27 | field=models.CharField(choices=[('ADMIN', 'Administrator'), ('ORGADMIN', 'Organization Administrator'), ('VIEWER', 'Viewer')], default='VIEWER', max_length=8), 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /src/django/users/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/django/users/migrations/__init__.py -------------------------------------------------------------------------------- /src/django/users/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /src/django/users/utils.py: -------------------------------------------------------------------------------- 1 | """Utility functions for User views/serializers""" 2 | 3 | from django.conf import settings 4 | from django.core.signing import TimestampSigner 5 | 6 | 7 | def get_password_reset_url(request, user): 8 | """Generates a password reset URL given a Request and user 9 | 10 | Args: 11 | request (rest_framework.request.Request) 12 | user (PBBUser): user to generate password reset url for 13 | """ 14 | signer = TimestampSigner(salt=settings.RESET_SALT) 15 | token = signer.sign('{}'.format(user.uuid)) 16 | return request.build_absolute_uri('/#/password-reset/?token={}'.format(token)) 17 | -------------------------------------------------------------------------------- /src/nginx/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:1.10 2 | 3 | MAINTAINER Azavea 4 | 5 | RUN mkdir -p /srv/dist && \ 6 | chown nginx:nginx -R /srv/dist/ 7 | 8 | RUN mkdir -p /srv/static && \ 9 | chown nginx:nginx -R /srv/static/ 10 | 11 | COPY srv/dist /srv/dist/ 12 | COPY srv/static /srv/static/ 13 | COPY etc/nginx/nginx.conf /etc/nginx/nginx.conf 14 | COPY etc/nginx/conf.d/default.conf /etc/nginx/conf.d/default.conf 15 | -------------------------------------------------------------------------------- /src/nginx/etc/nginx/nginx.conf: -------------------------------------------------------------------------------- 1 | user nginx; 2 | worker_processes 1; 3 | 4 | error_log /var/log/nginx/error.log warn; 5 | pid /var/run/nginx.pid; 6 | 7 | events { 8 | worker_connections 1024; 9 | } 10 | 11 | http { 12 | include /etc/nginx/mime.types; 13 | default_type application/octet-stream; 14 | 15 | log_format timed_combined '$remote_addr - $remote_user [$time_local] ' 16 | '"$request" $status $body_bytes_sent $gzip_ratio ' 17 | '"$http_referer" "$http_user_agent" ' 18 | '$request_time $upstream_response_time'; 19 | 20 | access_log /var/log/nginx/access.log timed_combined; 21 | 22 | sendfile on; 23 | 24 | keepalive_timeout 65; 25 | 26 | client_max_body_size 6m; 27 | 28 | server_tokens off; 29 | 30 | gzip on; 31 | gzip_types application/javascript text/css application/json; 32 | gzip_disable "msie6"; 33 | 34 | set_real_ip_from 10.0.0.0/24; 35 | set_real_ip_from 10.0.2.0/24; 36 | set_real_ip_from 10.0.1.0/24; 37 | set_real_ip_from 10.0.3.0/24; 38 | 39 | real_ip_header X-Forwarded-For; 40 | 41 | include /etc/nginx/conf.d/*.conf; 42 | } 43 | -------------------------------------------------------------------------------- /src/nginx/srv/dist/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/nginx/srv/dist/.gitkeep -------------------------------------------------------------------------------- /src/nginx/srv/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/azavea/pfb-network-connectivity/8a9ad2de238d3b024e8fc79ae0659db669d84cf3/src/nginx/srv/static/.gitkeep -------------------------------------------------------------------------------- /src/tilegarden/.env.dev: -------------------------------------------------------------------------------- 1 | AWS_PROFILE=pfb 2 | PFB_DB_HOST=database.service.pfb.internal 3 | PFB_DB_DATABASE=pfb 4 | PFB_DB_PASSWORD=pfb 5 | PFB_DB_PORT=5432 6 | PFB_DB_USER=pfb 7 | PFB_TILEGARDEN_CACHE_BUCKET=dev-pfb-tilecache-us-east-1 8 | -------------------------------------------------------------------------------- /src/tilegarden/.env.example: -------------------------------------------------------------------------------- 1 | # To import from the local environment rather than hard-coding, change to just 'AWS_PROFILE' 2 | AWS_PROFILE=pfb 3 | 4 | # Name of the lambda function. Should match the `tilegarden_function_name` Terraform variable. 5 | LAMBDA_FUNCTION_NAME= 6 | 7 | # Function config information 8 | ## REQUIRED ## 9 | LAMBDA_REGION= 10 | 11 | # Amount of time in seconds your lambdas will run before timing out. 12 | # Default is 3, so some override is necessary. 13 | LAMBDA_TIMEOUT= 14 | 15 | ## OPTIONAL ## 16 | # Memory in MB allocated to your lambda functions 17 | # More memory also brings more CPU and bandwidth. Default if not specified is 128. 18 | #LAMBDA_MEMORY=512 19 | # The following VPC (Virtual Private Cloud) settings should be used if you 20 | # need your lambdas to be able to connect to other AWS resources, 21 | # e.g. an RDS instance, and should match the subnets/security groups used 22 | # for those resources. 23 | # VPC Subnets that your lambdas should use (comma separated list) 24 | #LAMBDA_SUBNETS=subnet1,subnet2,subnet...N 25 | # VPC Security Groups that your lambdas should use (comma separated list) 26 | #LAMBDA_SECURITY_GROUPS=group1,group2,group...N 27 | 28 | # PFB-specific variables 29 | PFB_DB_DATABASE= 30 | PFB_DB_PASSWORD= 31 | PFB_DB_PORT= 32 | PFB_DB_USER= 33 | PFB_TILEGARDEN_CACHE_BUCKET= 34 | -------------------------------------------------------------------------------- /src/tilegarden/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "jest": true 4 | }, 5 | "extends": "airbnb-base", 6 | "rules": { 7 | "camelcase": 2, 8 | "indent": ["error", 4], 9 | "import/no-extraneous-dependencies": ["error", {"optionalDependencies": true}], 10 | "no-console": 0, 11 | "no-unexpected-multiline": 2, 12 | "max-len": ["error", 100, { "ignoreComments": true}], 13 | "max-statements": ["error", 50], 14 | "object-curly-newline": ["error", { "consistent": true }], 15 | "quotes": ["error", "single"], 16 | "semi": [2, "never"], 17 | "strict": 2, 18 | "vars-on-top": 2 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/tilegarden/.gitignore: -------------------------------------------------------------------------------- 1 | # Claudia configs 2 | claudia/ 3 | -------------------------------------------------------------------------------- /src/tilegarden/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:12.19-buster-slim 2 | 3 | ENV BASE_DIR /opt/pfb/tilegarden 4 | 5 | # Install git for git dependencies 6 | RUN apt-get update -y && apt-get install -y git jq python2 && apt-get clean 7 | RUN yarn global add carto 8 | 9 | # Copy files needed for installing packages first 10 | COPY package.json yarn.lock ${BASE_DIR}/ 11 | WORKDIR ${BASE_DIR}/ 12 | 13 | # install node modules 14 | RUN yarn install 15 | 16 | # Copy remaining files after package installation to benefit from layer caching 17 | COPY . ${BASE_DIR}/ 18 | 19 | CMD ["yarn", "dev"] 20 | -------------------------------------------------------------------------------- /src/tilegarden/scripts/build-all-xml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | function usage() { 6 | echo -n "Usage: $(basename "${0}") [input directory] [output directory] 7 | Transpiles a directory's worth of MML+MSS files into Tilegarden-readable XML 8 | Options: 9 | --help Display this help text 10 | " 11 | } 12 | 13 | function main() { 14 | for file in $(echo "${1}/*") 15 | do 16 | ext="${file#*.}" 17 | if [ "$ext" == "mml" ]; then 18 | # get output path 19 | filename="${file##*/}" 20 | base="${filename%%.*}" 21 | outPath="${2}/${base}.xml" 22 | 23 | mkdir -p ${2} 24 | 25 | echo "Transpiling ${file} => ${outPath}" 26 | ./scripts/build-xml.sh "${file}" > ${outPath} 27 | fi 28 | done 29 | } 30 | 31 | if [ "${BASH_SOURCE[0]}" = "${0}" ] 32 | then 33 | if [ "${1:-}" = "--help" ] 34 | then 35 | usage 36 | else 37 | main "$1" "$2" 38 | fi 39 | exit 40 | fi 41 | -------------------------------------------------------------------------------- /src/tilegarden/scripts/build-xml.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # This script is never meant to be called directly by the user. 4 | # It should only be called by other scripts that pass MML strings 5 | # in to it, so that different environments can have access to the 6 | # version of carto installed on the docker container. 7 | 8 | function main() { 9 | tempFile="${1%%.*}.temp.mml" 10 | 11 | # fill in environment variables 12 | node scripts/template-vars.js "${1}" > ${tempFile} 13 | 14 | # compile with carto 15 | carto ${tempFile} 16 | 17 | # clean up 18 | rm ${tempFile} 19 | } 20 | 21 | main "${1}" "${2}" 22 | -------------------------------------------------------------------------------- /src/tilegarden/scripts/deploy: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn claudia update --config claudia/claudia.json --no-optional-dependencies \ 4 | --runtime "provided" \ 5 | --layers "arn:aws:lambda:${LAMBDA_REGION}:553035198032:layer:nodejs12:36" \ 6 | ${LAMBDA_TIMEOUT:+--timeout ${LAMBDA_TIMEOUT}} \ 7 | ${LAMBDA_MEMORY:+--memory ${LAMBDA_MEMORY}} \ 8 | ${LAMBDA_SECURITY_GROUPS:+--security-group-ids ${LAMBDA_SECURITY_GROUPS}} \ 9 | ${LAMBDA_SUBNETS:+--subnet-ids ${LAMBDA_SUBNETS}} \ 10 | --set-env PFB_DB_HOST=${PFB_DB_HOST},PFB_DB_DATABASE=${PFB_DB_DATABASE},PFB_DB_PASSWORD=${PFB_DB_PASSWORD},PFB_DB_PORT=${PFB_DB_PORT},PFB_DB_USER=${PFB_DB_USER},${PFB_TILEGARDEN_CACHE_BUCKET:+PFB_TILEGARDEN_CACHE_BUCKET=${PFB_TILEGARDEN_CACHE_BUCKET}} 11 | -------------------------------------------------------------------------------- /src/tilegarden/scripts/deploy-new: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | yarn claudia create --config claudia/claudia.json --no-optional-dependencies \ 4 | --runtime "provided" \ 5 | --layers "arn:aws:lambda:${LAMBDA_REGION}:553035198032:layer:nodejs12:36" \ 6 | --api-module dist/api \ 7 | --name ${LAMBDA_FUNCTION_NAME} \ 8 | --region ${LAMBDA_REGION} \ 9 | --role "${LAMBDA_FUNCTION_NAME}Executor" \ 10 | ${LAMBDA_TIMEOUT:+--timeout ${LAMBDA_TIMEOUT}} \ 11 | ${LAMBDA_MEMORY:+--memory ${LAMBDA_MEMORY}} \ 12 | ${LAMBDA_SECURITY_GROUPS:+--security-group-ids ${LAMBDA_SECURITY_GROUPS}} \ 13 | ${LAMBDA_SUBNETS:+--subnet-ids ${LAMBDA_SUBNETS}} \ 14 | --set-env PFB_DB_HOST=${PFB_DB_HOST},PFB_DB_DATABASE=${PFB_DB_DATABASE},PFB_DB_PASSWORD=${PFB_DB_PASSWORD},PFB_DB_PORT=${PFB_DB_PORT},PFB_DB_USER=${PFB_DB_USER},${PFB_TILEGARDEN_CACHE_BUCKET:+PFB_TILEGARDEN_CACHE_BUCKET=${PFB_TILEGARDEN_CACHE_BUCKET}} 15 | -------------------------------------------------------------------------------- /src/tilegarden/scripts/template-vars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Stub script that places variable templates with the proper env variable. 3 | * In all likelihood this could be done with one line of sed but I was having 4 | * trouble getting the string replacement part to work out. 5 | */ 6 | 7 | const fs = require('fs') 8 | 9 | fs.readFile(process.argv[2], 'utf-8', function (err, out) { 10 | if (err) process.stderr.write(err) 11 | else { 12 | const templated = out.replace( 13 | /\$\{([a-z0-9_]+)\}/gi, 14 | (_, envName) => `"${process.env[envName]}"` 15 | ) 16 | 17 | process.stdout.write(templated) 18 | } 19 | }) 20 | -------------------------------------------------------------------------------- /src/tilegarden/src/config/mml/bike_infrastructure.mml: -------------------------------------------------------------------------------- 1 | name: Bike infrastructure 2 | srs: +init=epsg:3857 3 | format: png 4 | 5 | Stylesheet: 6 | - bike_infrastructure.mss 7 | 8 | Layer: 9 | - id: neighborhood_waysTF 10 | name: neighborhood_waysTF 11 | geometry: line 12 | srs: "+init=epsg:4326" 13 | Datasource: 14 | host: ${PFB_DB_HOST} 15 | dbname: ${PFB_DB_DATABASE} 16 | user: ${PFB_DB_USER} 17 | password: ${PFB_DB_PASSWORD} 18 | type: "postgis" 19 | table: "pfb_analysis_neighborhoodwaysresults" 20 | key_field: "" 21 | geometry_field: "geom" 22 | - id: neighborhood_waysFT 23 | name: neighborhood_waysFT 24 | geometry: line 25 | srs: "+init=epsg:4326" 26 | Datasource: 27 | host: ${PFB_DB_HOST} 28 | dbname: ${PFB_DB_DATABASE} 29 | user: ${PFB_DB_USER} 30 | password: ${PFB_DB_PASSWORD} 31 | type: "postgis" 32 | table: "pfb_analysis_neighborhoodwaysresults" 33 | key_field: "" 34 | geometry_field: "geom" 35 | -------------------------------------------------------------------------------- /src/tilegarden/src/config/mml/census_blocks.mml: -------------------------------------------------------------------------------- 1 | name: Census blocks 2 | srs: +init=epsg:3857 3 | format: png 4 | 5 | Stylesheet: 6 | - census_blocks.mss 7 | 8 | Layer: 9 | - id: neighborhood_census_blocks 10 | name: neighborhood_census_blocks 11 | geometry: polygon 12 | srs: "+init=epsg:4326" 13 | Datasource: 14 | host: ${PFB_DB_HOST} 15 | dbname: ${PFB_DB_DATABASE} 16 | user: ${PFB_DB_USER} 17 | password: ${PFB_DB_PASSWORD} 18 | type: "postgis" 19 | table: "pfb_analysis_censusblocksresults" 20 | key_field: "" 21 | geometry_field: "geom" 22 | -------------------------------------------------------------------------------- /src/tilegarden/src/config/mml/census_blocks.mss: -------------------------------------------------------------------------------- 1 | #neighborhood_census_blocks[overall_score != null] { 2 | polygon-opacity: 0.65; 3 | } 4 | 5 | #neighborhood_census_blocks[overall_score >= 0][overall_score < 10] { 6 | polygon-fill: #FF3300; 7 | } 8 | #neighborhood_census_blocks[overall_score >= 10][overall_score < 20] { 9 | polygon-fill: #D04628; 10 | } 11 | #neighborhood_census_blocks[overall_score >= 20][overall_score < 30] { 12 | polygon-fill: #B9503C; 13 | } 14 | #neighborhood_census_blocks[overall_score >= 30][overall_score < 40] { 15 | polygon-fill: #A25A51; 16 | } 17 | #neighborhood_census_blocks[overall_score >= 40][overall_score < 50] { 18 | polygon-fill: #8B6465; 19 | } 20 | #neighborhood_census_blocks[overall_score >= 50][overall_score < 60] { 21 | polygon-fill: #736D79; 22 | } 23 | #neighborhood_census_blocks[overall_score >= 60][overall_score < 70] { 24 | polygon-fill: #5C778D; 25 | } 26 | #neighborhood_census_blocks[overall_score >= 70][overall_score < 80] { 27 | polygon-fill: #4581A2; 28 | } 29 | #neighborhood_census_blocks[overall_score >= 80][overall_score < 90] { 30 | polygon-fill: #2E8BB6; 31 | } 32 | #neighborhood_census_blocks[overall_score >= 90][overall_score <= 100] { 33 | polygon-fill: #009FDF; 34 | } 35 | -------------------------------------------------------------------------------- /src/tilegarden/src/config/mml/ways.mml: -------------------------------------------------------------------------------- 1 | name: Neighborhood ways 2 | srs: +init=epsg:3857 3 | format: png 4 | 5 | Stylesheet: 6 | - ways.mss 7 | 8 | Layer: 9 | - id: neighborhood_waysTF 10 | name: neighborhood_waysTF 11 | geometry: line 12 | srs: "+init=epsg:4326" 13 | Datasource: 14 | host: ${PFB_DB_HOST} 15 | dbname: ${PFB_DB_DATABASE} 16 | user: ${PFB_DB_USER} 17 | password: ${PFB_DB_PASSWORD} 18 | type: "postgis" 19 | table: "pfb_analysis_neighborhoodwaysresults" 20 | key_field: "" 21 | geometry_field: "geom" 22 | - id: neighborhood_waysFT 23 | name: neighborhood_waysFT 24 | geometry: line 25 | srs: "+init=epsg:4326" 26 | Datasource: 27 | host: ${PFB_DB_HOST} 28 | dbname: ${PFB_DB_DATABASE} 29 | user: ${PFB_DB_USER} 30 | password: ${PFB_DB_PASSWORD} 31 | type: "postgis" 32 | table: "pfb_analysis_neighborhoodwaysresults" 33 | key_field: "" 34 | geometry_field: "geom" -------------------------------------------------------------------------------- /src/tilegarden/src/config/mml/ways.mss: -------------------------------------------------------------------------------- 1 | @width-tight: 2; 2 | @width-med: 1.5; 3 | @width-wide: 1; 4 | @color-low-stress: #009fdf; 5 | @color-high-stress: #ff3300; 6 | 7 | #neighborhood_waysFT, #neighborhood_waysTF { 8 | [zoom > 13] { 9 | line-width: @width-tight; 10 | } 11 | [zoom <= 13][zoom >= 10] { 12 | line-width: @width-med; 13 | } 14 | [zoom < 10] { 15 | line-width: @width-wide; 16 | } 17 | } 18 | 19 | #neighborhood_waysTF { 20 | [zoom > 13] { 21 | line-offset: -@width-tight; 22 | } 23 | [zoom <= 13][zoom >= 10] { 24 | line-offset: -@width-med; 25 | } 26 | [zoom < 10] { 27 | line-offset: -@width-wide; 28 | } 29 | [tf_seg_str > 1] { 30 | line-color: @color-high-stress; 31 | } 32 | [tf_seg_str = 1] { 33 | line-color: @color-low-stress; 34 | } 35 | [tf_seg_str < 1] { 36 | line-opacity: 0; 37 | } 38 | } 39 | 40 | #neighborhood_waysFT { 41 | [zoom > 13] { 42 | line-offset: @width-tight; 43 | } 44 | [zoom <= 13][zoom >= 10] { 45 | line-offset: @width-med; 46 | } 47 | [zoom < 10] { 48 | line-offset: @width-wide; 49 | } 50 | [ft_seg_str > 1] { 51 | line-color: @color-high-stress; 52 | } 53 | [ft_seg_str = 1] { 54 | line-color: @color-low-stress; 55 | } 56 | [ft_seg_str < 1] { 57 | line-opacity: 0; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/tilegarden/src/util/error-builder.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds an error object that stores an HTTP error code 3 | * @private 4 | * @param text - Error message. 5 | * @param status - HTTP status 6 | * @returns {Error} 7 | */ 8 | module.exports = (text, status) => { 9 | const error = new Error(text) 10 | error.http_code = status 11 | return error 12 | } 13 | -------------------------------------------------------------------------------- /src/tilegarden/src/util/logger.js: -------------------------------------------------------------------------------- 1 | /* Creates and exports a logger that logs to console and prepends a timestamp. 2 | * Default log level is 'info', switching to 'debug' if DEBUG is true in the environment. 3 | */ 4 | 5 | const { createLogger, format, transports } = require('winston') 6 | 7 | const logger = createLogger({ 8 | level: process.env.DEBUG ? 'debug' : 'info', 9 | format: format.combine( 10 | format.timestamp(), 11 | format.printf(({ timestamp, level, message }) => `${level}:\t${timestamp} ${message}`), 12 | ), 13 | transports: [new transports.Console()], 14 | }) 15 | 16 | module.exports = logger 17 | -------------------------------------------------------------------------------- /src/tilegarden/src/util/xml-tools.js: -------------------------------------------------------------------------------- 1 | /* Helper functions to encapsulate converting to and from XML. 2 | * 3 | * There's not much here, but it does get reused, and having it centralized should make it 4 | * easier to change libraries or settings if necessary. 5 | */ 6 | 7 | const { promisify } = require('util') 8 | const xml2js = require('xml2js') 9 | 10 | const logger = require('./logger') 11 | 12 | // Make an async-friendly version of the parser 13 | const parsePromise = promisify(xml2js.parseString) 14 | 15 | async function parseXml(xmlString) { 16 | logger.debug('parseXml') 17 | return parsePromise(xmlString) 18 | } 19 | 20 | function buildXml(xmlJsObj) { 21 | logger.debug('buildXml') 22 | const builder = new xml2js.Builder() 23 | return builder.buildObject(xmlJsObj) 24 | } 25 | 26 | module.exports = { 27 | parseXml, 28 | buildXml, 29 | } 30 | -------------------------------------------------------------------------------- /src/tilegarden/tests/error-builder.test.js: -------------------------------------------------------------------------------- 1 | const HTTPError = require('../src/util/error-builder') 2 | 3 | describe('Error builder', () => { 4 | test('Check that an HTTPError gets built properly', () => { 5 | const message = 'This function doesn\'t really need testing, but I want Jest to be happy.' 6 | const code = 401 7 | 8 | const error = HTTPError(message, code) 9 | expect(error).toBeInstanceOf(Error) 10 | expect(error.http_code).toBe(code) 11 | expect(error.message).toBe(message) 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /src/verifier/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3-slim 2 | 3 | MAINTAINER Azavea 4 | 5 | RUN apt-get update && apt-get install -y --no-install-recommends \ 6 | python3-pip \ 7 | build-essential \ 8 | && rm -rf /var/lib/apt/lists/* 9 | 10 | COPY requirements.txt /tmp/ 11 | RUN pip3 install --upgrade pip && pip3 install --no-cache-dir -r /tmp/requirements.txt 12 | 13 | COPY ./ /opt/verifier 14 | 15 | WORKDIR /opt/verifier 16 | 17 | ENTRYPOINT ["./compare_outputs.sh"] 18 | 19 | CMD [] 20 | -------------------------------------------------------------------------------- /src/verifier/compare_outputs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | DEFAULT_OUPUT='analysis_neighborhood_score_inputs.csv' 4 | 5 | USAGE="usage: $(basename {$0}) VERIFIED_FILE [FILE_TO_VERIFY] 6 | 7 | VERIFIED_FILE is name of a CSV in the verified_output directory 8 | FILE_TO_VERIFY is name of a CSV in the data/output directory; defaults to ${DEFAULT_OUPUT} 9 | " 10 | 11 | if [ $# -eq 0 ]; then 12 | echo "Missing argument for name of existing CSV file from verified_output to check against." 13 | exit 1 14 | fi 15 | 16 | if [[ "$1" == "-h" || "$1" == "--help" ]]; then 17 | echo $USAGE 18 | exit 0 19 | fi 20 | 21 | VERIFIED=verified_output/$1 22 | UNVERIFIED=output/${2:-$DEFAULT_OUPUT} 23 | 24 | echo "Comparing ${VERIFIED} to ${UNVERIFIED}" 25 | 26 | csvdiff -q id ${VERIFIED} ${UNVERIFIED} 27 | 28 | if [ $? -eq 0 ]; then 29 | echo "Output matches!" 30 | exit 0 31 | else 32 | echo "Output mismatch:" 33 | csvdiff id --style summary ${VERIFIED} ${UNVERIFIED} 34 | echo "" 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /src/verifier/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '2' 2 | services: 3 | verifier: 4 | image: pfb-verifier 5 | build: 6 | context: . 7 | dockerfile: Dockerfile 8 | volumes: 9 | - ../../data/output:/opt/verifier/output 10 | - ../../verified_output:/opt/verifier/verified_output -------------------------------------------------------------------------------- /src/verifier/requirements.txt: -------------------------------------------------------------------------------- 1 | csvdiff==0.3.1 2 | --------------------------------------------------------------------------------