Giter VIP home page Giter VIP logo

greeninfo-network / nyc-crash-mapper Goto Github PK

View Code? Open in Web Editor NEW
16.0 4.0 4.0 2.46 MB

React Redux web application for viewing, filtering, & comparing trends of automobile collision data published by the NYPD.

Home Page: http://www.crashmapper.org

License: MIT License

CSS 24.66% JavaScript 73.07% HTML 2.11% Shell 0.17%
civictech nyc crash-reporting map carto civic-tech react redux visualization webmapping

nyc-crash-mapper's Introduction

NYC CRASH MAPPER

Web application that geographically maps, filters, and aggregates NYC automobile collision data. Built with CARTO, ES6, React, Redux, Leaflet, and Webpack2.

This work was funded by CHEKPEDS, a 501(c)(3) non-profit organization that was founded in 2005 and advocates for pedestrian safety, primarily focusing on the west side of Manhattan.

NYC Crash Mapper was designed and built by Chris Henrick.

Install

Installation of this app requires knowledge of the command line interface and a shell application such as Terminal on Mac OS.

App was developed using NodeJS @ ^6.7, NPM @ ^3.10, and Yarn @ ^0.21.3.

It is recommended to use Node Version Manager (nvm) and Yarn to ensure compatibility with NodeJS and dependencies in project.json.

Assuming nvm is available globally, do:

nvm use

If you get an error such as

N/A: version v6.7.0 is not yet installed

Then do:

nvm install 6.7

To install the app's dependencies do:

yarn -i

Develop

To start the webpack dev server and run the app locally do:

npm run serve

And open your browser to localhost:8080. Any changes made to the app's code base will cause Webpack to recompile the source code and refresh the browser. ES Lint will report any javascript errors as well as complain about broken style rules.

NOTE: Running this app locally assumes that the companion app, nyc-crash-mapper-chart-view is also running locally on a separate port to allow debugging between both apps. Thus, the navigation list items for trend, compare, and rank will link to localhost:8889. When the app is deployed, these navigation list items will link to vis.crashmapper.org.

Build

To compile the source code do:

npm run build

The compiled code will be available in the dist/ directory.

Deploy

Currently the app is being hosted using Github Pages.

To Github Pages

Be sure this folder is under version control using git and is pointing to a remote repository on Github.

Enable permissions to read/execute the deploy_gh_pages.sh bash script:

chmod u+rx deploy_gh_pages.sh

Then do:

npm run deploy:gh-pages

That will execute the build script then the bash script, creating a Github Pages site with the content of the dist directory.

NOTE: Doing this will remove the custom domain name (crashmapper.org) from the repo's settings. You will need to manually add it back after redeploying.

About the Data

NYC Crash Mapper uses vehicle collision data for New York City from August, 2011 - present. The data was aggregated and normalized from the following sources:

The complete dataset may be downloaded via the Chekpeds CARTO account.

At the time of launch, the combined data contained 1,075,468 rows, of which 157,554 rows were not geocoded due to the source data lacking values for latitude and longitude or adequate address attributes. The majority of non-geocoded rows come from the NYC Open Data.

Prior to importing the data into CARTO, rows lacking lat lon with potentially valid address information were attempted to be geocoded using the NYC GeoClient API. For example, if a row lacked values for lat lon, but contained values for cross streets and borough or zip code, the NYC GeoClient intersection endpoint was used to geocode the crash location.

Stats displayed in the bottom UI for number of total crashes, injuries, and fatalities will differ from what is shown on the map when the app's "Filter By Boundary" is set to "Citywide." Stats display counts for all collision data, whether geocoded or not, while the map only displays data that has valid lat lon coordinates and thus can be geographically mapped. When a "Filter By Boundary" setting other than "Citywide" is used, such as a "Community Board", the stats UI will only display counts for data that has been geocoded and falls within a selected boundary.

We include non-geocoded data in the app's "citywide" query to advocate for the NYPD getting its act together to improve the quality of NYC's vehicle collision data. This means: geocoding all crash locations (not just 75% of them), providing values for contributing factors, and providing values for vehicle type for all crashes.

Because the portion of the data which comes from the NYPD Crash Data Bandaid is summarized by month, each row for the dataset contains a value for crash_count. The only rows that have a value higher than 1 for this field are for rows that correspond to the NYPD Crash Data Bandaid, which are from July 2011 to June 2012. Unfortunately this limits the app to filtering data by month and year, rather than day and time.

Data Updates

Collision data is updated using an ETL Script that runs daily, consuming data from the NYC Open Data Portal, formatting it for the NYC Crash Mapper crash data table, and inserting it using the CARTO SQL API if the data does not currently exist in CARTO.

Notes on importing data into CARTO

Because the app's database folds values from multiple columns for the contributing factor and vehicle type categories into single Postgres text array columns for each category, exporting the data from a local PostgreSQL db and importing it into CARTO requires special attention. CARTO will not recognize array columns when importing data so you must do some data processing to get them to be recognized as such. See 2016_data_update.sql for how this was accomplished in March 2017.

App Structure

This app is written in ES6, and compiles to ES5 JavaScript via Babel using Webpack 2. The application's point of entry is ./src/index.jsx.

Redux

Redux uses the concept of keeping all application state within an immutable store and returning new application state via action creators. Reducers trigger changes in the Store after receiving Actions from Action Creators. When the app loads its store is hydrated from query params in the URL hash if they are present. This easily allows for the sharing of application state between users via the app's URL.

React

All UI components are built using React, which allow for transforming application data into UI views.

This project follows the React Redux concept of using "Containers" which may be connected to the Redux store and/or action creators via React-Redux. Regular components receive data from Containers or parent components as props, and only use Component level state for trivial UI changes, eg: tracking whether or not a UI panel is collapsed or opened.

The main scaffolding of the app resides within src/components/App.jsx. The app is connected to Redux via Provider and rendered to the DOM within ./src/index.jsx.

React Component Tree

The following describes how React Components are nested within the app:

  • AppConnected*

    • App

      • AppHeader

        • MenuConnected*

          • Menu
      • LeafletMapConnected*

        • LeafletMap/index

          • customFilter (es6 class, but not a react component)

          • ZoomControls

      • StatsLegendConnected*

        • StatsLegend/index

          • ContributingFactorsList

          • LegendContainer

          • StatsCounter

          • TotalCrashesCounter

      • OptionsFiltersConnected*

        • OptionsFilters/index

          • OptionsContainer

          • FilterByAreaConnected*

            • FilterByArea
          • FilterByTypeConnected*

            • FilterByType
          • FilterByDateConnected*

            • FilterByDate
          • DownloadData

          • ShareOptions

          • FooterOptions

      • ModalConnected*

        • Modal/index

          • About

            • AboutCopy
          • ModalContent

          • Copyright

          • Disclaimer

          • DownloadData

          • ShareURL

          • ShareFB

          • ShareTwitter

      • SmallDeviceMessage

      • LoadingIndicator

* means Component is a Container Component connected to the Redux Store.

Configuration

This app uses CARTO as a backend datastore and web map tile generator. The CARTO user name, data tables, and other app configuration parameters are specified in ./src/constants/app_config.js.

Note that any tables used by the app must be set to either "Public" or "With Link" via the CARTO dashboard.

SQL

PostgreSQL and PostGIS power the app via CARTO.

The app uses ES6 Template Literals to generate SQL queries based on values from the Redux Store. These are available in ./src/constants/sql_queries.js.

Sample SQL queries are stored in ./sql are for reference and are not used by the app during runtime.

Sass

This app uses SASS which compiles down to regular CSS. The Sass is organized into partials that map to specific React components in ./scss/components/.

Skeleton

The app uses a slightly modified Sass version of the Skeleton CSS framework.

nyc-crash-mapper's People

Contributors

clhenrick avatar danrademacher avatar gregallensworth avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

nyc-crash-mapper's Issues

Update SQL queries to match new crashes table schema

Final data is almost ready to be imported to CARTO and loaded into the app. Will need to update each of the SQL queries to reflect the changed schema for the crashes table.

  • do sum(crash_count) for total crashes instead of doing count(cartodb_id) as total_crashes

  • will have to do something like count(unnest(contributing_factor)) now that there is a single field for contributing factors that is of the type text array.

  • For filtering by date range will have to select by year and month columns instead of using date_val column (relates to #36)

last updated date: make feature or remove

App currently says: "Map data last updated 12/31/2016", either remove or add a feature that alters this date when the ETL script runs and successfully updates the data on Carto.

Write Documentation

update README to include description of the project, that it's a React Redux app using CARTO, etc.

Dynamically update CartoCSS and Legend after a Filter is applied?

Currently the crash dot sizes are hard coded. It may make sense to compute equal interval or quantile breaks when the range of the crash count changes after a filter is applied.

For example, currently when all years of the dataset are selected the max crash count for one location is 415, but when a single year or single month is selected the max would be substantially lower.

Refactor Filter By Type UI, Action Creators, & Reducer

Currently the UI is problematic:

If I select a person type such as "cyclist", but not a injury type such as "injury" or "fatality", it becomes much more difficult to filter, and more confusing to the user. In the case of cyclists we can use the vehicle type code to see all collisions where a cyclist was involved but not necessarily injured or killed. However there is no such code to filter for pedestrians who may have been involved in a crash but not injured or killed. Likewise selecting "motorist" without selecting "injury" or "fatality" or "no injury / fatality" would really just mean selecting every single crash, as all crashes typically involve motorists. We could theoretically select crashes that involved 2 or more cyclists, but there is no way to select a crash that only involved cyclists and pedestrians.

Thus, it would make more sense to refactor the UI as follows:

label button button button
Cyclist : fatality injury
Pedestrian : fatality injury
Motorist : fatality injury other
  • "other" would select all crashes with no injuries or fatalities
  • users could select multiple buttons, eg: fatalities for cyclists & pedestrians

Mobile landing page

Display a landing / greeting page for users visiting on mobile asking them to view on Desktop. Mobile layout will be integrated in a later of phase of work.

Filter by boundary interaction steps

  1. When a user clicks a boundary filter button, such as borough, the corresponding GeoJSON data should be loaded on the map as a L.geoJson layer.

    • store.filterArea.geo will be set to boundary type. Will require mapping between button name & boundary table name in Carto
  2. Clicking on a polygon of the boundary layer will trigger the filtering of the crash data using the corresponding polygon geometry.

    • store.filterArea.identifier will be set to id of boundary polygon.
  3. The map should zoom & pan to the bounding box / extent of that polygon.

  4. Polygon data should then be removed from map to not block interactions with crash data

  5. To the right of Filter Button should display the identifier of selected polygon, eg: 101 or Brookyln or Crown Heights

Once GeoJSON is fetched for a polygon it can be kept in the Redux store so that it doesn't need to be fetched again.

Q: If user is zoomed in to an area and selects a boundary filter, should map zoom to extent of the data?

Filter by custom area interaction will be similar, but without the ajax fetch.

Modal

Modal Component needed for:

  • Disclaimer
  • About
  • Copyright
  • Download Data

Application State & state-full URLs

Parts of application state to consider, with state-full URLs in mind:

  • map zoom & center (will live in URL hash only)
  • crash type: fatality, injury, no fatality or injury, or all
  • person type: pedestrian, cyclist, motorist, or all
  • area filter: borough, precinct, community board, ..., custom
    • areas with set boundary can have a set identifier code or number, eg: 1 for Manhattan (borough)
    • custom will require a list of lat lon points representing the custom shape
  • start date (date format)
  • end date (date format)

State-full URL will only affect app state when page is loaded or refreshed.

To think about:

  • how the application state determine the CARTO SQL query
  • default application state (date range & probably "city wide" enabled)

Set up custom domain name

To be provided by client.

If hosting on github pages and redirecting URL, will need to make sure all links are changed from https to http. Also site name should be updated in social media meta tags as it is currently pointing to https://clhenrick.github.io/nyc-crash-mapper/.

  • turn off https option in initCartoLayer
  • use http version of carto basemap tiles url in app_config
  • use // for google fonts css in index.html
  • use http version of cartodb.js css in index.html
  • use http version of cartob.js in index.html

Make date range filter UI select by year month

In order to use the data compiled by John Krauss for 2009 - 2011 the date range filter should be limited to month and year. That data is summary data and fills in the gap for some years between the TA data and NYC Open Data.

About the data

Write up a statement that explains:

  • data sources: NYC Open Data Portal, NY Crash Data Bandaid, TA FOIL of NY DMV data
  • data normalization methodology
  • why data in map doesn't reflect data in stats (see #27)
  • etc...

Map Legend

  • Possibly add "Number of Crashes" to indicate size represents crash count
  • Fix CSS to correctly position circles and text for legend, possibly transform: translate(x, y) ?
  • Move "LEGEND" text to the right to align with color circles for crash type

Querying by date range does not include values matching endDate

For example when selecting all data from July 1st โ€“ July 31st, the query returns data up to but not inclusive of July 31st.

Sample query:

select distinct date_val from export2016_07
where date_val >= date '2016-07-01'
AND date_val <= date '2016-07-31'
order by date_val desc

Escape quotes in SQL queries

Certain values have single quotes in them like Hell's Kitchen which is a value of the "identifier" field for the nyc_neighborhood table.

"persons_"* fields don't seem to always match total counts for person types

Some rows have higher values for the type of person injured or killed than "number_of_persons_killed" or "number_of_persons_injured". For example, a row may have a value of 3 for "number_of_pedestrians_killed" but then have a value of 2 or 1 or 0 for "number_of_persons_killed". Typically in the data it seems that "number_of_persons_killed" reflects a total for all three categories of person killed: cyclist, motorist, and pedestrian. Same for "number_of_persons_injured".

As such the columns "number_of_persons_injured" and "number_of_persons_killed" should be treated with suspect.

Here is a screenshot of selecting such rows in CARTO:

screen shot 2017-01-30 at 1 00 38 pm

Show disclaimer message about data in stats vs map

When Citywide boundary filter is enabled, display a disclaimer message to the right of the cartodb attribution stating that stats may differ than points on map due to lack of location information provided by the NYPD.

Add "active" state for FilterButtons

When a user clicks on one it should stay active until another button is selected or it is clicked again.

Filter by Type will allow for both a crash type and person type to be selected.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.