Deploying to gh-pages from @ usds/justice40-tool@3c3a8b0637 🚀

This commit is contained in:
NatHillardUSDS 2021-05-25 03:09:50 +00:00
commit 690739ca02
150 changed files with 29 additions and 41095 deletions

View file

@ -1 +0,0 @@
docs/decisions

129
.gitignore vendored
View file

@ -1,129 +0,0 @@
*.env
.idea
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/

File diff suppressed because one or more lines are too long

1
404.html Normal file

File diff suppressed because one or more lines are too long

1
404/index.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -1,13 +0,0 @@
# Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers at justice40open@usds.gov.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)

View file

@ -1,48 +0,0 @@
# Contributing to the Justice40 Tool
🎉 First off, thanks for taking the time to contribute! 🎉
The following is a set of guidelines for contributing to the Justice40 Tool that lives in this repository.
Before contributing, we encourage you to also read our [LICENSE](https://github.com/usds/justice40-tool/blob/main/LICENSE) and [README](https://github.com/usds/justice40-tool/blob/main/README) files, also found in this repository. If you have any questions not answered by the content of this repository, please don't hesitate to [contact us](mailto:justice40open@usds.gov).
## Public Domain
This project is in the public domain within the United States, and copyright and related rights in the work worldwide are waived through the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/).
All contributions to this project will be released under the CC0 dedication. By submitting a pull request you are agreeing to comply with this waiver of copyright interest.
## How Can I Contribute?
### Report a bug
If you think you have found a bug in the Justice40 tool, search our issues list on GitHub in case a similar issue has already been opened.
When reporting the bug, please follow these guidelines:
- **Please use the `Bug Report` issue template** ([here](https://github.com/usds/justice40-tool/issues/new/choose)). This is populated with the right information
- **Use a clear and descriptive issue title** for the issue to identify the problem.
- **Describe the exact steps which reproduce the problem** in as many details as possible. For example, start by explaining how you got to the page where you encountered the bug.
- **Describe the behavior you observed after following the steps** and point out what exactly is the problem with that behavior.
- **Explain which behavior you expected to see instead and why.**
- **Include screenshots and animated GIFs** if possible, which show you following the described steps and clearly demonstrate the problem.
- **If the problem wasn't triggered by a specific action**, describe what you were doing before the problem happened.
### Suggest an enhancement
If you don't have specific language or code to submit but would like to suggest a change, request a feature,
or have something addressed, you can open an issue in this repository.
Please open an issue of type "Feature request" [here](https://github.com/usds/justice40-tool/issues/new/choose).
In the issue, please describe the feature you would like to see, why you need it, and how it should work. Team members will respond to the issue as soon as possible.
### Submit a pull request
If you would like to contribute, please submit a pull request. In order for us to merge a pull request, the following checks are enabled within this repo:
- Merges to `main` are prohibited - please open a pull request from a branch
- At least one required reviewer must approve the commit (see [CODEOWNERS](https://github.com/usds/justice40-tool/tree/main/.github/CODEOWNERS) for the most up-to-date list of these members)
- All required status checks must pass
If there is significant dissent within the team, a meeting will be held to discuss a plan of action for the pull request.

View file

@ -1,25 +0,0 @@
## This project is in the worldwide public domain
As a work of the United States government, this project is in the public domain within the United States.
Additionally, we waive copyright and related rights in the work worldwide through the [CC0 1.0 Universal public domain dedication](https://creativecommons.org/publicdomain/zero/1.0/).
### CC0 1.0 Universal Summary
This is a human-readable summary of the [Legal Code (read the full text)](https://creativecommons.org/publicdomain/zero/1.0/legalcode).
#### No Copyright
The person who associated a work with this deed has dedicated the work to the public domain by waiving all of his, her, or their rights to the work worldwide under copyright law, including all related and neighboring rights, to the extent allowed by law.
You can copy, modify, distribute and perform the work, even for commercial purposes, all without asking permission.
#### Other Information
In no way are the patent or trademark rights of any person affected by CC0, nor are the rights that other persons may have in the work or in how the work is used, such as publicity or privacy rights.
Unless expressly stated otherwise, the person who associated a work with this deed makes no warranties about the work, and disclaims liability for all uses of the work, to the fullest extent permitted by applicable law. When using or citing the work, you should not imply endorsement by the author or the affirmer.
### Contributions to this project
As stated in [CONTRIBUTING](CONTRIBUTING.md), all contributions to this project will be released under the CC0 dedication. By submitting a pull request, you are agreeing to comply with this waiver of copyright interest.

View file

@ -1,17 +0,0 @@
# Justice40 Tool
[![CC0 License](https://img.shields.io/badge/license-CCO--1.0-brightgreen)](https://github.com/usds/justice40-tool/blob/main/LICENSE)
This repo contains the code for the working prototype and soon to be MVP of the Justice40 Climate, Environmental, and Economic Justice Screeing Tool. The Justice40 initiative and screening tool were announced in an [Executive Order](https://www.whitehouse.gov/briefing-room/presidential-actions/2021/01/27/executive-order-on-tackling-the-climate-crisis-at-home-and-abroad/) in January 2021, with the goal to launch an MVP of the tool by July 27, 2021. This tool will include interactive maps and an initial draft scorecard by which federal agencies can identify historically overburdened and underserved communities for benefits in their programs.
The Justice40 team is taking a community-first and open source approach to the product development of this tool. We encourage contributions in the form of discussion on issues in this repo and pull requests of documentation and code. Please see the [Contributing](#Contributing) section for more information.
## Contributing
Contributions are always welcome!
See `CONTRIBUTING.md` for ways to get started.
## Feedback
If you have any feedback or questions, please reach out to us at justice40open@usds.gov.

File diff suppressed because one or more lines are too long

1
chunk-map.json Normal file
View file

@ -0,0 +1 @@
{"polyfill":["/polyfill-aab98ca4729baf333612.js"],"app":["/app-404d7df65849d4125f83.js"],"component---src-pages-404-tsx":["/component---src-pages-404-tsx-0559dc84bfa6590824cc.js"],"component---src-pages-index-tsx":["/component---src-pages-index-tsx-a57013ff3500d3516c77.js"]}

3
client/.gitignore vendored
View file

@ -1,3 +0,0 @@
node_modules/
.cache/
public

View file

@ -1,34 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "chrome",
"request": "launch",
"name": "Debug Chrome",
"url": "http://localhost:8000",
"webRoot": "${workspaceFolder}"
},
{
"name": "Gatsby develop",
"type": "node",
"request": "launch",
"protocol": "inspector",
"program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby",
"args": ["develop"],
"stopOnEntry": false,
"runtimeArgs": ["--nolazy"],
"sourceMaps": false
},
{
"name": "Gatsby build",
"type": "node",
"request": "launch",
"protocol": "inspector",
"program": "${workspaceRoot}/node_modules/gatsby/dist/bin/gatsby",
"args": ["build"],
"stopOnEntry": false,
"runtimeArgs": ["--nolazy"],
"sourceMaps": false
}
]
}

View file

@ -1,34 +0,0 @@
# Justice40 Client
## Localization
### About
This project uses [Gatsby Plugin Intl](https://www.gatsbyjs.com/plugins/gatsby-plugin-intl/?=intl) to manage internationalization and localization.
There are a number of components to this, but for the purposes of localization, this utizes the popular `react-intl` package from [FormatJS](https://github.com/formatjs/formatjs).
This works by directing users to a locale-appropriate version of the page they wish to visit based on their browser settings, populated automatically at build time by the contents of `json` files in the `src/intl` directory.
### Writing
For this library to work optimally, the following principles should be obeyed (see [here](https://formatjs.io/docs/getting-started/message-extraction) for more detail):
- All user-visible strings should be wrapped with the `intl.formatMessage` function or the `<FormattedMessage>` tag, with a `description` and `defaultMessage` set. Do not yet set the "id" tag, it will be generated for you. To generate files for localization, run `npm run intl:extract` to update the file at `src/intl/en.json` with the extracted contents of all `FormattedMessage` components.
- Take note of the `id` in this file, and add this back as a parameter of your function/prop for your component (this is done to avoid naming collisions as detailed [here](https://formatjs.io/docs/getting-started/message-extraction))
- All `Link` components should be imported from `gatsby-plugin-intl` instead to get the locale-appropriate link
- All pages should import and use `useIntl` from `gatsby-plugin-intl`
We will later add integration with Github Actions to ensure that all messages have been formatted as a condition/check for committed code.
### Translating
From there, send `src/intl/en.json` to translators. (Depending on the TMS (Translation Management System) in use, we may need a different format, so we can alter the settings in `package.json` if needbe). When they return with the other language file, e.g. `es.json`, place this in `src/intl/` as a sibling to `en.json`.
### Consuming
`React-Intl` works according to Google SEO [best practices](https://developers.google.com/search/docs/advanced/crawling/managing-multi-regional-sites#use-different-urls-for-different-language-versions) by creating locale-specific URLs.
To access a translated version of a page, e.g. `pages/index.js`, add the locale as a portion of the URL path, as follows:
- English: `localhost:8000/en/`, or `localhost:8000/` (the default fallback is English)

View file

@ -1 +0,0 @@
import './src/styles/global.scss';

View file

@ -1,40 +0,0 @@
module.exports = {
/*
This is to workaround the following error when building locally:
Warning: React.createElement: type is invalid -- expected a string
(for built-in components) or a class/function (for composite components) but got: undefined.
at IndexPage
We will need to fix this before running `gatsby build`
*/
flags: {
DEV_SSR: false
},
siteMetadata: {
title: "Justice40",
},
plugins: [
{
resolve: 'gatsby-plugin-sass',
options: {
cssLoaderOptions: {
modules: {
exportLocalsConvention: 'camelCaseOnly'
}
}
}
},
{
resolve: `gatsby-plugin-intl`,
options: {
// language JSON resource path
path: `${__dirname}/src/intl`,
// supported language
languages: [`en`, `es`],
// language file path
defaultLanguage: `en`,
// option to redirect to `/en` when connecting `/`
redirect: true,
},
},
],
};

View file

@ -1,21 +0,0 @@
path = require('path');
// Disable references to window from trussworks/uswds
// See here: https://www.gatsbyjs.com/docs/debugging-html-builds/#fixing-third-party-modules
exports.onCreateWebpackConfig = ({ stage, loaders, actions }) => {
actions.setWebpackConfig({
devtool: 'eval-source-map',
});
if (stage === "build-html" || stage === "develop-html") {
actions.setWebpackConfig({
module: {
rules: [
{
test: /@trussworks\/react-uswds/,
use: loaders.null(),
},
],
}
})
}
}

35066
client/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -1,36 +0,0 @@
{
"name": "justice40-tool",
"version": "1.0.0",
"private": true,
"description": "Justice40 Tool",
"author": "Nat Hillard",
"keywords": [
"gatsby"
],
"scripts": {
"develop": "gatsby develop",
"start": "gatsby develop",
"build": "gatsby build",
"serve": "gatsby serve",
"clean": "gatsby clean",
"intl:extract": "formatjs extract 'src/(pages|components)/*.tsx' --out-file src/intl/en.json",
"intl:compile": "formatjs compile src/intl/en.json --ast --out-file compiled-lang/en.json"
},
"devDependencies": {
"@formatjs/cli": "^4.2.15",
"@types/node": "^15.3.1",
"@types/react": "^17.0.1",
"@types/react-dom": "^17.0.1",
"gatsby-cli": "^3.5.0",
"gatsby-plugin-intl": "^0.3.3",
"gatsby-plugin-sass": "^4.5.0",
"sass": "^1.33.0",
"sass-loader": "^11.1.1"
},
"dependencies": {
"@trussworks/react-uswds": "^1.17.0",
"gatsby": "^3.4.1",
"react": "^17.0.1",
"react-dom": "^17.0.1"
}
}

View file

@ -1,17 +0,0 @@
import React from 'react';
import { Footer } from '@trussworks/react-uswds';
import {Link} from 'gatsby-plugin-intl';
const footerLinks = [
<Link to="/">Home</Link>
];
const J40Footer = () => {
return (
<>
<Footer primary={[]} secondary={footerLinks} />
</>
);
};
export default J40Footer;

View file

@ -1,25 +0,0 @@
import React from 'react';
import { GovBanner, Header, Title, PrimaryNav } from '@trussworks/react-uswds';
import { useIntl, Link } from "gatsby-plugin-intl"
const headerLinks = [
<Link to="/">Home</Link>
];
const J40Header = () => {
const intl = useIntl()
const title = intl.formatMessage({ id: "71L0pp", defaultMessage:"Justice40", description:"Title of the project" });
return (
<>
<GovBanner />
<Header>
<Title>
{title}
</Title>
<PrimaryNav items={headerLinks}/>
</Header>
</>
);
};
export default J40Header;

View file

@ -1,9 +0,0 @@
.site {
display: flex;
min-height: 100vh;
flex-direction: column;
}
.site-content {
flex: 1;
}

View file

@ -1,13 +0,0 @@
declare namespace LayoutModuleScssNamespace {
export interface ILayoutModuleScss {
site: string;
siteContent: string;
}
}
declare const LayoutModuleScssModule: LayoutModuleScssNamespace.ILayoutModuleScss & {
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
locals: LayoutModuleScssNamespace.ILayoutModuleScss;
};
export = LayoutModuleScssModule;

View file

@ -1,19 +0,0 @@
import React, { ReactNode } from "react";
import * as styles from './layout.module.scss';
import J40Header from './J40Header';
import J40Footer from "./J40Footer";
interface ILayoutProps {
children: ReactNode
}
const Layout = ({ children }: ILayoutProps) => {
return (
<div className={styles.site}>
<J40Header />
<div className={styles.siteContent}>{children}</div>
<J40Footer />
</div>
);
};
export default Layout;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View file

@ -1,6 +0,0 @@
{
"71L0pp": {
"defaultMessage": "Justice40",
"description": "Title of the project"
}
}

View file

@ -1,3 +0,0 @@
{
"71L0pp": "Justicia40"
}

View file

@ -1,54 +0,0 @@
import * as React from "react";
import { Link } from "gatsby-plugin-intl";
// styles
const pageStyles = {
color: "#232129",
padding: "96px",
fontFamily: "-apple-system, Roboto, sans-serif, serif",
}
const headingStyles = {
marginTop: 0,
marginBottom: 64,
maxWidth: 320,
}
const paragraphStyles = {
marginBottom: 48,
}
const codeStyles = {
color: "#8A6534",
padding: 4,
backgroundColor: "#FFF4DB",
fontSize: "1.25rem",
borderRadius: 4,
}
// markup
const NotFoundPage = () => {
return (
<main style={pageStyles}>
<title>Not found</title>
<h1 style={headingStyles}>Page not found</h1>
<p style={paragraphStyles}>
Sorry{" "}
<span role="img" aria-label="Pensive emoji">
😔
</span>{" "}
we couldnt find what you were looking for.
<br />
{process.env.NODE_ENV === "development" ? (
<>
<br />
Try creating a page in <code style={codeStyles}>src/pages/</code>.
<br />
</>
) : null}
<br />
<Link to="/">Go home</Link>.
</p>
</main>
)
}
export default NotFoundPage

View file

@ -1,15 +0,0 @@
import * as React from "react"
import Layout from '../components/layout';
// markup
const IndexPage = () => {
return (
<Layout>
<main>
<h2>Subheader</h2>
</main>
</Layout>
)
}
export default IndexPage;

View file

@ -1,2 +0,0 @@
@import "~@trussworks/react-uswds/lib/uswds.css";
@import "~@trussworks/react-uswds/lib/index.css";

View file

@ -1,17 +0,0 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es6",
"jsx": "preserve",
"lib": ["dom", "es2015", "es2017"],
"strict": true,
"noEmit": true,
"isolatedModules": true,
"esModuleInterop": true,
"skipLibCheck": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"removeComments": false
},
"include": ["./src/**/*"]
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,153 +0,0 @@
# Overview
This document describes our "data roadmap", which serves several purposes.
# Data roadmap goals
The goals of the data roadmap are as follows:
- Tracking data sets being considered for inclusion in the Climate and Economic Justice Screening Tool (CEJST), either as a data set that is included in the cumulative impacts score or a reference data set that is not included in the score
- Prioritizing data sets, so that it's obvious to developers working on the CEJST which data sets to incorporate next into the tool
- Gathering important details about each data set, such as its geographic resolution and the year it was last updated, so that the CEJST team can make informed decisions about what data to prioritize
- Tracking the problem areas that each data set relates to (e.g., a certain data set may relate to the problem of pesticide exposure amongst migrant farm workers)
- Enabling members of the public to submit ideas for problem areas or data sets to be considered for inclusion in the CEJST, with easy-to-use and accessible tools
- Enabling members of the public to submit revisions to the information about each problem area or data set, with easy-to-use and accessible tools
- Enabling the CEJST development team to review suggestions before incorporating them officially into the data roadmap, to filter out potential noise and spam, or consider how requests may lead to changes in software features and documentation
# User stories
These goals can map onto several user stories for the data roadmap, such as:
- As a community member, I want to suggest a new idea for a dataset.
- As a community member, I want to understand what happened with my suggestion for a new dataset.
- As a community member, I want to edit the details of a dataset proposal to add more information.
- As a WHEJAC board member, I want to vote on what data sources should be prioritized next.
- As a product manager, I want to filter based on characteristics of the data.
- As a developer, I want to know what to work on next.
# Data set descriptions
There are lots of details that are important to track for each data set. This
information helps us prepare to integrate a data set into the tool and prioritize
between different options for data in the data roadmap.
In order to support a process of peer review on edits and updates, these details are
tracked in one `YAML` file per data set description in the directory
[data_roadmap/data_set_descriptions](data_roadmap/data_set_descriptions).
Each data set description includes a number of fields, some of which are required.
The schema defining these fields is written in [Yamale](https://github.com/23andMe/Yamale)
and lives at [data_roadmap/data_set_description_schema.yaml](data_roadmap/data_set_description_schema.yaml).
Because `Yamale` does not provide a method for describing fields, we've created an
additional file that includes written descriptions of the meaning of each field in
the schema. These live in [data_roadmap/data_set_description_field_descriptions.yaml](data_roadmap/data_set_description_field_descriptions.yaml).
In order to provide a helpful starting point for people who are ready to contribute
ideas for a new data set for consideration, there is an auto-generated data set
description template that lives at [data_roadmap/data_set_description_template.yaml](data_roadmap/data_set_description_template.yaml).
# Steps to add a new data set description: the "easy" way
Soon we will create a Google Form that contributors can use to submit ideas for new
data sets. The Google Form will match the schema of the data set descriptions. Please
see [this ticket](https://app.zenhub.com/workspaces/justice40-60993f6e05473d0010ec44e3/issues/usds/justice40-tool/39)
for tracking this work.
# Steps to add a new data set description: the git-savvy way
For those who are comfortable using `git` and `Markdown`, these are the steps to
contribute a new data set description to the data roadmap:
1. Research and learn about the data set you're proposing for consideration.
2. Clone the repository and learn about the [contribution guidelines for this
project](../docs/CONTRIBUTING.md).
3. In your local version of the repository, copy the template from
`data_roadmap/data_set_description_template.yaml` into a new file that lives in
`data_roadmap/data_set_descriptions` and has the name of the data set as the name of the file.
4. Edit this file to ensure it has all of the appropriate details about the data set.
5. If you'd like, you can run the validations in `run_validations_and_write_template`
to ensure your contribution is valid according to the schema. These checks will also
run automatically on each commit.
6. Create a pull request with your new data set description and submit it for peer
review.
Thank you for contributing!
# Tooling proposal and milestones
There is no single tool that supports all the goals and user stories described above.
Therefore we've proposed combining a number of tools in a way that can support them all.
We've also proposed various "milestones" that will allow us to iteratively and
sequentially build the data roadmap in a way that supports the entire vision but
starts with small and achievable steps. These milestones are proposed in order.
This work is most accurately tracked in [this epic](https://app.zenhub.com/workspaces/justice40-60993f6e05473d0010ec44e3/issues/usds/justice40-tool/38).
We've also verbally described them below.
## Milestone: YAML files for data sets and linter (Done)
To start, we'll create a folder in this repository that can
house YAML files, one per data set. Each file will describe the characteristics of the data.
The benefit of using a YAML file for this is that it's easy to subject changes to these files to peer review through the pull request process. This allows external collaborators from the open source community to submit suggested changes, which can be reviewed by the core CEJST team.
We'll use a Python-based script to load all the files in the directory, and then run a schema validator to ensure all the files have valid entries.
For schema validation, we propose using [Yamale](https://github.com/23andMe/Yamale). This provides a lightweight schema and validator, and [integrates nicely with GitHub actions](https://github.com/nrkno/yaml-schema-validator-github-action).
If there's an improper format in any of the files, the schema validator will throw an error.
As part of this milestone, we will also set this up to run automatically with each commit to any branch as part of CI/CD.
## Milestone: Google forms integration
To make it easy for non-engineer members of the public and advisory bodies such as the WHEJAC to submit suggestions for data sets, we will configure a Google Form that maps to the schema of the data set files.
This will enable members of the public to fill out a simple form suggesting data without needing to understand Github or other engineering concepts.
At first, these responses can just go into a resulting Google Sheet and be manually copied and converted into data set description files. Later, we can write a script that converts new entries in the Google Sheet automatically into data set files. This can be setup to run as a trigger on the addition of new rows to the Google Sheet.
## Milestone: Post data in tabular format
Add a script that runs the schema validator on all files and, if successful, posts the results in a tabular format. There are straightforward packages to post a Python dictionary / `pandas` dataframe to Google Sheets and/or Airtable. As part of this milestone, we will also set this up to run automatically with each commit to `main` as part of CI/CD.
This will make it easier to filter the data to answer questions like, "which data sources are available at the census block group level".
## Milestone: Tickets created for incorporating data sets
For each data set that is being considered for inclusion soon in the tool, the project management team will create a ticket for "Incorporating \_\_\_ data set into the database", with a link to the data set detail document. This ticket will be created in the ticket tracking system used by the open source repository, which is ZenHub. This project management system will be public.
At the initial launch, we are not planning for members of the open source community to be able to create tickets, but we would like to consider a process for members of the open source community creating tickets that can go through review by the CEJST team.
This will help developers know what to work on next, and open source community members can also pick up tickets and work to integrate the data sets.
## Milestone: Add problem areas
We'll need to somehow track "problem areas" that describe problems in climate, environmental, and economic justice, even without specific proposals of data sets. For instance, a problem area may be "food insecurity", and a number of data sets can have this as their problem area.
We can change the linter to validate that every data set description maps to one or more known problem areas.
The benefit of this is that some non-data-focused members of the public or the WHEJAC advisory body may want to suggest we prioritize certain problem areas, with or without ideas for specific data sets that may best address that problem area.
It is not clear at this time the best path forward for implementing these problem area descriptions. One option is to create a folder for descriptions of problem areas, which contains YAML files that get validated according to a schema. Another option would be simply to add these as an array in the description of data sets, or add labels to the tickets once data sets are tracked in GitHub tickets.
## Milestone: Add prioritzation voting for WHEJAC and members of the public
This milestone is currently the least well-defined. It's important that members of advisory bodies like the WHEJAC and members of the public be able to "upvote" certain data sets for inclusion in the tool.
One potential for this is to use the [Stanford Participatory Budgeting Platform](https://pbstanford.org/). Here's an [example of voting on proposals within a limited budget](https://pbstanford.org/nyc8/knapsack).
For instance, going into a quarterly planning cycle, the CEJST development team could estimate the amount of time (in developer-weeks) that it would take to clean, analyze, and incorporate each potential data set. For instance, incorporating some already-cleaned census data may take 1 week of a developer's time, while incorporating new asthma data from CMS that's never been publicly released could take 5 weeks. Given a "budget" of the number of developer weeks available (e.g., 2 developers for 13 weeks, or 26 developer-weeks), advisors can vote on their top priorities for inclusion in the tool within the available "budget".

View file

@ -1,39 +0,0 @@
# There is no method for adding field descriptions to `yamale` schemas.
# Therefore, we've created a dictionary here of fields and their descriptions.
name: A short name of the data set.
source: The URL pointing towards the data set itself or more information about the
data set.
relevance_to_environmental_justice: It's useful to spell out why this data is
relevant to EJ issues and/or can be used to identify EJ communities.
spatial_resolution: Dev team needs to know if the resolution is granular enough to be useful
public_status: Whether a dataset has already gone through public release process
(like Census data) or may need a lengthy review process (like Medicaid data).
sponsor: Whether there's a federal agency or non-governmental agency that is working
to provide and maintain this data.
subjective_rating_of_data_quality: Sometimes we don't have statistics on data
quality, but we know it is likely to be accurate or not. How much has it been
vetted by an agency; is this the de facto data set for the topic?
estimated_margin_of_error: Estimated margin of error on measurement, if known. Often
more narrow geographic measures have a higher margin of error due to a smaller sample
for each measurement.
known_data_quality_issues: It can be helpful to write out known problems.
geographic_coverage_percent: We want to think about data that is comprehensive across
America.
geographic_coverage_description: A verbal description of geographic coverage.
data_formats: Developers need to know what formats the data is available in
last_updated_date: When was the data last updated / refreshed? (In format YYYY-MM-DD.
If exact date is not known, use YYYY-01-01.)
frequency_of_updates: How often is this data updated? Is it updated on a reliable
cadence?
documentation: Link to docs. Also, is the documentation good enough? Can we get the
info we need?
data_can_go_in_cloud: Some datasets can not legally go in the cloud
discussion: Review of other topics, such as
peer review (Overview or links out to peer review done on this dataset),
where and how data is available (e.g., Geoplatform.gov? Is it available from multiple
sources?),
risk assessment of the data (e.g. a vendor-processed version of the dataset might not
be open or good enough),
legal considerations (Legal disclaimers, assumption of risk, proprietary?),
accreditation (Is this source accredited?)

View file

@ -1,24 +0,0 @@
# `yamale` schema for descriptions of data sets.
name: str(required=True)
source: str(required=True)
relevance_to_environmental_justice: str(required=False)
data_formats: enum('GeoJSON', 'Esri Shapefile (SHP, DBF, SHX)', 'GML', 'KML/KMZ',
'GPX', 'CSV/XLSX', 'GDB', 'MBTILES', 'LAS', required=True)
spatial_resolution: enum('State/territory', 'County', 'Zip code', 'Census tract',
'Census block group', 'Exact address or lat/long', 'Other', required=True)
public_status: enum('Not Released', 'Public', 'Public for certain audiences', 'Other',
required=True)
sponsor: str(required=True)
subjective_rating_of_data_quality: enum('Low Quality', 'Medium Quality', 'High
Quality', required=False)
estimated_margin_of_error: num(required=False)
known_data_quality_issues: str(required=False)
geographic_coverage_percent: num(required=False)
geographic_coverage_description: str(required=False)
last_updated_date: day(min='2001-01-01', max='2100-01-01', required=True)
frequency_of_updates: enum('Less than annually', 'Approximately annually',
'Once very 1-6 months',
'Daily or more frequently than daily', 'Unknown', required=True)
documentation: str(required=False)
data_can_go_in_cloud: bool(required=False)
discussion: str(required=False)

View file

@ -1,94 +0,0 @@
# Note: This template is automatically generated by the function
# `write_data_set_description_template_file` from the schema
# and field descriptions files. Do not manually edit this file.
name:
# Description: A short name of the data set.
# Required field: True
# Field type: str
source:
# Description: The URL pointing towards the data set itself or more information about the data set.
# Required field: True
# Field type: str
relevance_to_environmental_justice:
# Description: It's useful to spell out why this data is relevant to EJ issues and/or can be used to identify EJ communities.
# Required field: False
# Field type: str
data_formats:
# Description: Developers need to know what formats the data is available in
# Required field: True
# Field type: enum
# Valid choices are one of the following: ('GeoJSON', 'Esri Shapefile (SHP, DBF, SHX)', 'GML', 'KML/KMZ', 'GPX', 'CSV/XLSX', 'GDB', 'MBTILES', 'LAS')
spatial_resolution:
# Description: Dev team needs to know if the resolution is granular enough to be useful
# Required field: True
# Field type: enum
# Valid choices are one of the following: ('State/territory', 'County', 'Zip code', 'Census tract', 'Census block group', 'Exact address or lat/long', 'Other')
public_status:
# Description: Whether a dataset has already gone through public release process (like Census data) or may need a lengthy review process (like Medicaid data).
# Required field: True
# Field type: enum
# Valid choices are one of the following: ('Not Released', 'Public', 'Public for certain audiences', 'Other')
sponsor:
# Description: Whether there's a federal agency or non-governmental agency that is working to provide and maintain this data.
# Required field: True
# Field type: str
subjective_rating_of_data_quality:
# Description: Sometimes we don't have statistics on data quality, but we know it is likely to be accurate or not. How much has it been vetted by an agency; is this the de facto data set for the topic?
# Required field: False
# Field type: enum
# Valid choices are one of the following: ('Low Quality', 'Medium Quality', 'High Quality')
estimated_margin_of_error:
# Description: Estimated margin of error on measurement, if known. Often more narrow geographic measures have a higher margin of error due to a smaller sample for each measurement.
# Required field: False
# Field type: num
known_data_quality_issues:
# Description: It can be helpful to write out known problems.
# Required field: False
# Field type: str
geographic_coverage_percent:
# Description: We want to think about data that is comprehensive across America.
# Required field: False
# Field type: num
geographic_coverage_description:
# Description: A verbal description of geographic coverage.
# Required field: False
# Field type: str
last_updated_date:
# Description: When was the data last updated / refreshed? (In format YYYY-MM-DD. If exact date is not known, use YYYY-01-01.)
# Required field: True
# Field type: day
frequency_of_updates:
# Description: How often is this data updated? Is it updated on a reliable cadence?
# Required field: True
# Field type: enum
# Valid choices are one of the following: ('Less than annually', 'Approximately annually', 'Once very 1-6 months', 'Daily or more frequently than daily', 'Unknown')
documentation:
# Description: Link to docs. Also, is the documentation good enough? Can we get the info we need?
# Required field: False
# Field type: str
data_can_go_in_cloud:
# Description: Some datasets can not legally go in the cloud
# Required field: False
# Field type: bool
discussion:
# Description: Review of other topics, such as peer review (Overview or links out to peer review done on this dataset), where and how data is available (e.g., Geoplatform.gov? Is it available from multiple sources?), risk assessment of the data (e.g. a vendor-processed version of the dataset might not be open or good enough), legal considerations (Legal disclaimers, assumption of risk, proprietary?), accreditation (Is this source accredited?)
# Required field: False
# Field type: str

View file

@ -1,35 +0,0 @@
name: Particulate Matter 2.5
source: https://gaftp.epa.gov/EJSCREEN/
relevance_to_environmental_justice: Particulate matter has a lot of adverse impacts
on health.
data_formats: CSV/XLSX
spatial_resolution: Census block group
public_status: Public
sponsor: EPA
subjective_rating_of_data_quality: Medium Quality
estimated_margin_of_error:
known_data_quality_issues: Many PM 2.5 stations are known to be pretty far apart, so
averaging them can lead to data quality loss.
geographic_coverage_percent:
geographic_coverage_description:
last_updated_date: 2017-01-01
frequency_of_updates: Less than annually
documentation: https://www.epa.gov/sites/production/files/2015-05/documents/ejscreen_technical_document_20150505.pdf#page=13
data_can_go_in_cloud: True
discussion:

View file

@ -1,151 +0,0 @@
import importlib_resources
import pathlib
import yamale
import yaml
# Set directories.
DATA_ROADMAP_DIRECTORY = importlib_resources.files("data_roadmap")
UTILS_DIRECTORY = DATA_ROADMAP_DIRECTORY / "utils"
DATA_SET_DESCRIPTIONS_DIRECTORY = DATA_ROADMAP_DIRECTORY / "data_set_descriptions"
# Set file paths.
DATA_SET_DESCRIPTION_SCHEMA_FILE_PATH = (
DATA_ROADMAP_DIRECTORY / "data_set_description_schema.yaml"
)
DATA_SET_DESCRIPTION_FIELD_DESCRIPTIONS_FILE_PATH = (
DATA_ROADMAP_DIRECTORY / "data_set_description_field_descriptions.yaml"
)
DATA_SET_DESCRIPTION_TEMPLATE_FILE_PATH = (
DATA_ROADMAP_DIRECTORY / "data_set_description_template.yaml"
)
def load_data_set_description_schema(
file_path: pathlib.PosixPath = DATA_SET_DESCRIPTION_SCHEMA_FILE_PATH,
) -> yamale.schema.schema.Schema:
"""Load from file the data set description schema."""
schema = yamale.make_schema(path=file_path)
return schema
def load_data_set_description_field_descriptions(
file_path: pathlib.PosixPath = DATA_SET_DESCRIPTION_FIELD_DESCRIPTIONS_FILE_PATH,
) -> dict:
"""Load from file the descriptions of fields in the data set description."""
# Load field descriptions.
with open(file_path, "r") as stream:
data_set_description_field_descriptions = yaml.safe_load(stream=stream)
return data_set_description_field_descriptions
def validate_descriptions_for_schema(
schema: yamale.schema.schema.Schema,
field_descriptions: dict,
) -> None:
"""Validate descriptions for schema.
Checks that every field in the `yamale` schema also has a field
description in the `field_descriptions` dict.
"""
for field_name in schema.dict.keys():
if field_name not in field_descriptions:
raise ValueError(
f"Field `{field_name}` does not have a "
f"description. Please add one to file `{DATA_SET_DESCRIPTION_FIELD_DESCRIPTIONS_FILE_PATH}`"
)
for field_name in field_descriptions.keys():
if field_name not in schema.dict.keys():
raise ValueError(
f"Field `{field_name}` has a description but is not in the " f"schema."
)
def validate_all_data_set_descriptions(
data_set_description_schema: yamale.schema.schema.Schema,
) -> None:
"""Validate data set descriptions.
Validate each file in the `data_set_descriptions` directory the schema
against the provided schema.
"""
data_set_description_file_paths_generator = DATA_SET_DESCRIPTIONS_DIRECTORY.glob(
"*.yaml"
)
# Validate each file
for file_path in data_set_description_file_paths_generator:
print(f"Validating {file_path}...")
# Create a yamale Data object
data_set_description = yamale.make_data(file_path)
# TODO: explore collecting all errors and raising them at once. - Lucas
yamale.validate(schema=data_set_description_schema, data=data_set_description)
def write_data_set_description_template_file(
data_set_description_schema: yamale.schema.schema.Schema,
data_set_description_field_descriptions: dict,
template_file_path: str = DATA_SET_DESCRIPTION_TEMPLATE_FILE_PATH,
) -> None:
"""Write an example data set description with helpful comments."""
template_file_lines = []
# Write comments at the top of the template
template_file_lines.append(
"# Note: This template is automatically generated by the function\n"
"# `write_data_set_description_template_file` from the schema\n"
"# and field descriptions files. Do not manually edit this file.\n\n"
)
schema_dict = data_set_description_schema.dict
for field_name, field_schema in schema_dict.items():
template_file_lines.append(f"{field_name}: \n")
template_file_lines.append(
f"# Description: {data_set_description_field_descriptions[field_name]}\n"
)
template_file_lines.append(f"# Required field: {field_schema.is_required}\n")
template_file_lines.append(f"# Field type: {field_schema.get_name()}\n")
if type(field_schema) is yamale.validators.validators.Enum:
template_file_lines.append(
f"# Valid choices are one of the following: {field_schema.enums}\n"
)
# Add an empty linebreak to separate fields.
template_file_lines.append("\n")
with open(template_file_path, "w") as file:
file.writelines(template_file_lines)
def run_validations_and_write_template() -> None:
"""Run validations of schema and descriptions, and write a template file."""
# Load the schema and a separate dictionary
data_set_description_schema = load_data_set_description_schema()
data_set_description_field_descriptions = (
load_data_set_description_field_descriptions()
)
validate_descriptions_for_schema(
schema=data_set_description_schema,
field_descriptions=data_set_description_field_descriptions,
)
# Validate all data set descriptions in the directory against schema.
validate_all_data_set_descriptions(
data_set_description_schema=data_set_description_schema
)
# Write an example template for data set descriptions.
write_data_set_description_template_file(
data_set_description_schema=data_set_description_schema,
data_set_description_field_descriptions=data_set_description_field_descriptions,
)
if __name__ == "__main__":
run_validations_and_write_template()

View file

@ -1,248 +0,0 @@
import unittest
from unittest import mock
import yamale
from data_roadmap.utils.utils_data_set_description_schema import (
load_data_set_description_schema,
load_data_set_description_field_descriptions,
validate_descriptions_for_schema,
validate_all_data_set_descriptions,
write_data_set_description_template_file,
)
class UtilsDataSetDescriptionSchema(unittest.TestCase):
@mock.patch("yamale.make_schema")
def test_load_data_set_description_schema(self, make_schema_mock):
load_data_set_description_schema(file_path="mock.yaml")
make_schema_mock.assert_called_once_with(path="mock.yaml")
@mock.patch("yaml.safe_load")
def test_load_data_set_description_field_descriptions(self, yaml_safe_load_mock):
# Note: this isn't a great test, we could mock the actual YAML to
# make it better. - Lucas
mock_dict = {
"name": "The name of the thing.",
"age": "The age of the thing.",
"height": "The height of the thing.",
"awesome": "The awesome of the thing.",
"field": "The field of the thing.",
}
yaml_safe_load_mock.return_value = mock_dict
field_descriptions = load_data_set_description_field_descriptions()
yaml_safe_load_mock.assert_called_once()
self.assertDictEqual(field_descriptions, mock_dict)
def test_validate_descriptions_for_schema(self):
# Test when all descriptions are present.
field_descriptions = {
"name": "The name of the thing.",
"age": "The age of the thing.",
"height": "The height of the thing.",
"awesome": "The awesome of the thing.",
"field": "The field of the thing.",
}
schema = yamale.make_schema(
content="""
name: str()
age: int(max=200)
height: num()
awesome: bool()
field: enum('option 1', 'option 2')
"""
)
# Should pass.
validate_descriptions_for_schema(
schema=schema, field_descriptions=field_descriptions
)
field_descriptions_missing_one = {
"name": "The name of the thing.",
"age": "The age of the thing.",
"height": "The height of the thing.",
"awesome": "The awesome of the thing.",
}
# Should fail because of the missing field description.
with self.assertRaises(ValueError) as context_manager:
validate_descriptions_for_schema(
schema=schema, field_descriptions=field_descriptions_missing_one
)
# Using `assertIn` because the file path is returned in the error
# message, and it varies based on environment.
self.assertIn(
"Field `field` does not have a description. Please add one to file",
str(context_manager.exception),
)
field_descriptions_extra_one = {
"name": "The name of the thing.",
"age": "The age of the thing.",
"height": "The height of the thing.",
"awesome": "The awesome of the thing.",
"field": "The field of the thing.",
"extra": "Extra description.",
}
# Should fail because of the extra field description.
with self.assertRaises(ValueError) as context_manager:
validate_descriptions_for_schema(
schema=schema, field_descriptions=field_descriptions_extra_one
)
# Using `assertIn` because the file path is returned in the error
# message, and it varies based on environment.
self.assertEquals(
"Field `extra` has a description but is not in the schema.",
str(context_manager.exception),
)
def test_validate_all_data_set_descriptions(self):
# Setup a few examples of `yamale` data *before* we mock the `make_data`
# function.
valid_data = yamale.make_data(
content="""
name: Bill
age: 26
height: 6.2
awesome: True
field: option 1
"""
)
invalid_data_1 = yamale.make_data(
content="""
name: Bill
age: asdf
height: 6.2
awesome: asdf
field: option 1
"""
)
invalid_data_2 = yamale.make_data(
content="""
age: 26
height: 6.2
awesome: True
field: option 1
"""
)
# Mock `make_data`.
with mock.patch.object(
yamale, "make_data", return_value=None
) as yamale_make_data_mock:
schema = yamale.make_schema(
content="""
name: str()
age: int(max=200)
height: num()
awesome: bool()
field: enum('option 1', 'option 2')
"""
)
# Make the `make_data` method return valid data.
yamale_make_data_mock.return_value = valid_data
# Should pass.
validate_all_data_set_descriptions(data_set_description_schema=schema)
# Make some of the data invalid.
yamale_make_data_mock.return_value = invalid_data_1
# Should fail because of the invalid field values.
with self.assertRaises(yamale.YamaleError) as context_manager:
validate_all_data_set_descriptions(data_set_description_schema=schema)
self.assertEqual(
str(context_manager.exception),
"""Error validating data
age: 'asdf' is not a int.
awesome: 'asdf' is not a bool.""",
)
# Make some of the data missing.
yamale_make_data_mock.return_value = invalid_data_2
# Should fail because of the missing fields.
with self.assertRaises(yamale.YamaleError) as context_manager:
validate_all_data_set_descriptions(data_set_description_schema=schema)
self.assertEqual(
str(context_manager.exception),
"""Error validating data
name: Required field missing""",
)
@mock.patch("builtins.open", new_callable=mock.mock_open)
def test_write_data_set_description_template_file(self, builtins_writelines_mock):
schema = yamale.make_schema(
content="""
name: str()
age: int(max=200)
height: num()
awesome: bool()
field: enum('option 1', 'option 2')
"""
)
data_set_description_field_descriptions = {
"name": "The name of the thing.",
"age": "The age of the thing.",
"height": "The height of the thing.",
"awesome": "The awesome of the thing.",
"field": "The field of the thing.",
}
write_data_set_description_template_file(
data_set_description_schema=schema,
data_set_description_field_descriptions=data_set_description_field_descriptions,
template_file_path="mock_template.yaml",
)
call_to_writelines = builtins_writelines_mock.mock_calls[2][1][0]
self.assertListEqual(
call_to_writelines,
[
"# Note: This template is automatically generated by the function\n"
"# `write_data_set_description_template_file` from the schema\n"
"# and field descriptions files. Do not manually edit this file.\n\n",
"name: \n",
"# Description: The name of the thing.\n",
"# Required field: True\n",
"# Field type: str\n",
"\n",
"age: \n",
"# Description: The age of the thing.\n",
"# Required field: True\n",
"# Field type: int\n",
"\n",
"height: \n",
"# Description: The height of the thing.\n",
"# Required field: True\n",
"# Field type: num\n",
"\n",
"awesome: \n",
"# Description: The awesome of the thing.\n",
"# Required field: True\n",
"# Field type: bool\n",
"\n",
"field: \n",
"# Description: The field of the thing.\n",
"# Required field: True\n",
"# Field type: enum\n",
"# Valid choices are one of the following: ('option 1', 'option 2')\n",
"\n",
],
)

View file

@ -1 +0,0 @@
yamale==3.0.6

View file

@ -1,21 +0,0 @@
"""Setup script for `data_roadmap` package."""
import os
from setuptools import find_packages
from setuptools import setup
# TODO: replace this with `poetry`. https://github.com/usds/justice40-tool/issues/57
_PACKAGE_DIRECTORY = os.path.abspath(os.path.dirname(__file__))
with open(os.path.join(_PACKAGE_DIRECTORY, "requirements.txt")) as f:
requirements = f.readlines()
setup(
name="data_roadmap",
description="Data roadmap package",
author="CEJST Development Team",
author_email="justice40open@usds.gov",
install_requires=requirements,
include_package_data=True,
packages=find_packages(),
)

View file

@ -1,8 +0,0 @@
# Documentation
Here you'll find all Justice40 project documentation.
In particular:
- [Decisions](decisions) - Documentation of significant project decisions
- [Data Roadmap](data-roadmap.md) - a description of our process for adding new datasets to our backlog

View file

@ -1,21 +0,0 @@
# Architecture
The below is a general architecture of our proposed system:
![Architecture](architecture-mmd.svg)
## Updating the Diagram
**Note: Do Not directly modify the svg file, it is generated automatically!**
In the event that you are interested in updating the architecture of our system, please go through the Architecture Decision Record Process (see [here](https://github.com/usds/justice40-tool/tree/main/docs/decisions) for more detail on this process).
Provided you have already done this, however, and/or would like to make small changes to the diagram itself, please read on!
This diagram was generated from a text-based markdown-like file using [MermaidJS](https://mermaid-js.github.io/) syntax and the [Compile Mermaid Markdown](https://github.com/marketplace/actions/compile-mermaid-markdown) Github Action.
To update, consult Mermaid syntax [here](https://mermaid-js.github.io/mermaid/#/flowchart) and update the `architecture.mmd` file.
To preview your changes, see Use Cases and Integrations [here](https://mermaid-js.github.io/mermaid/#/integrations) in order to setup editor integration.
Once you are satisfied with your change, create a pull request. The `compile_mermaid.yml` workflow ([here](https://github.com/usds/justice40-tool/blob/main/.github/workflows/compile_mermaid.yml)) will automatically generate and commit an SVG version of your diagram with the name `X.svg`.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 30 KiB

View file

@ -1,39 +0,0 @@
graph LR
subgraph c["Community"]
input["Community Input"]
end
subgraph ds["Data Selection"]
input --> Intake
input --> Evolution
input --> Voting
Intake --> Evolution --> Voting
end
subgraph s["Geoplatform.gov"]
subgraph dp["Data Pipeline (Justice40 Repo)"]
Voting --> a["Approved Datasets"]
a --> Properties
a --> Geometries
Properties --> Processing
Geometries --> Processing
input --> Processing
end
subgraph Server
Processing --> GeoJSON
GeoJSON --> db[("Feature Database")]
db --> tileserv["Tile Server"]
end
subgraph j40["Justice40 Client"]
tileserv --> vl
ts["Tile Styling"] --> vl["Justice40 Visualization Library"]
vl --> fe["Justice40 Static Site Frontend"]
end
end
subgraph oc["Other Clients"]
tileserv -- API --> 3p["Third Party Apps"]
GeoJSON -- API --> 3p
db -- API --> 3p
end

View file

@ -1,19 +0,0 @@
# 1. Record architecture decisions
Date: 2021-04-21
## Status
Accepted
## Context
We need to record the architectural decisions made on this project.
## Decision
We will use Architecture Decision Records, as [described by Michael Nygard](http://thinkrelevance.com/blog/2011/11/15/documenting-architecture-decisions).
## Consequences
See Michael Nygard's article, linked above. For a lightweight ADR toolset, see Nat Pryce's [adr-tools](https://github.com/npryce/adr-tools).

View file

@ -1,12 +0,0 @@
(From https://github.com/Malvoz/web-maps-wcag-evaluation/blob/master/README.md) 1.1.1 Non-text Content (A) Result 1.1.1 Non-text Content (A) Notes 1.3.1 Info and Relationships (A) Result 1.3.1 Info and Relationships (A) Notes 1.4.3 Contrast (Minimum) Result 1.4.3 Contrast (Minimum) Notes 2.1.1 Keyboard (A) Result 2.1.1 Keyboard (A) Notes 2.1.2 No Keyboard Trap (A) Result 2.1.4 Character Key Shortcuts (A) Result 2.1.4 Character Key Shortcuts (A) Notes 2.4.3 Focus Order (A) Result 2.4.3 Focus Order (A) Notes 2.4.7 Focus Visible (AA) Result 2.4.7 Focus Visible (AA) Notes 2.5.5 Target Size (AAA) Result 2.5.5 Target Size (AAA) Notes 3.1.2 Language of Parts (AA) Result 3.2.2 On Input (A) Result 3.2.5 Change on Request (AAA) Result 3.2.5 Change on Request (AAA) Notes 4.1.2 Name, Role, Value (A) Result 4.1.2 Name, Role, Value (A) Notes Success Count
Google Maps embed 0 The image depicting "Google" (logo) is neither hidden from ATs nor has a text alternative. 0 The zoom controls' disabled state cannot be programmatically determined. 0 The "View larger map" link has a contrast ratio of 3.79. Some map text (i.e. ocean labels) do not mean the minimum contrast ratio. 0 Map display is not pannable using a keyboard. Control to toggle between "Satellite imagery" and "Street map" is not keyboard operable. 1 N/A (No single key shortcuts available.) 0 ChromeVox (no announcement) "Terms of use link" "View larger map link" "Zoom in button" "Zoom out button" (no announcement) NVDA "Google Maps frame clickable" "Terms of use link" "clickable View larger map link" "Zoom in button Zoom in" "Zoom out button Zoom out" "clickable" 0 2 tab stops without focus indicators. 0 4/4 targets are too small. 0 1 0 Both the "View larger map" link and the "Terms of Use" link open in new windows without warning. The "View larger map" link is especially unexpected as stylistically, it resembles a button, not a link. 0 The "map component" (<div tabindex="0">, which acts as a control to zoom the map display) is missing name and role. Control to toggle between "Satellite imagery" and "Street map" is missing name and role. 2
Google Maps Platform API 0 The link that opens a new window to view the current location (in "Street view") is visually presented as an icon, but is missing a text alternative. 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. The zoom controls' disabled state cannot be programmatically determined. The fullscreen control's pressed state cannot be programmatically determined. 0 The "View larger map" link has a contrast ratio of 3.79. Some map text (i.e. ocean labels) do not mean the minimum contrast ratio. 0 Control to initiate "Street view" (so called "Pegman") is only draggable using a mouse, and not keyboard operable. The "Labels" toggle is only accessible in a drop-down after hovering a mouse pointer over the "Satellite" button. The "Terrain" toggle is only accessible in a drop-down after hovering a mouse pointer over the "Map" button. Control to exit "Street view" (<div jsaction="closeControl.click">) is not keyboard operable. 1 1 0 ChromeVox (no announcement) "Open this area in Google Maps, opens a new window link" "Terms of use link" "Toggle fullscreen view button" "Zoom in button" "Zoom out button" "Show street map button pressed" "Show satellite imagery button not pressed" NVDA "clickable" "Open this area in Google Maps, opens a new window link" "Terms of use link" "Toggle fullscreen view button toggle fullscreen view" "Zoom in button zoom in" "Zoom out button zoom out" "clickable Show street map toggle button pressed show street map" "clickable Show satellite imagery toggle button not pressed show satellite imagery" 0 13 tab stops without focus indicators (in "Street view"). 0 14/14 targets are too small. 0 0 0 The "Terms of Use" link opens in new window without warning. The Google logo link contains the screen reader label "Open this area in Google Maps", which does give the user sufficient warning, as it may open in the native Maps app on mobile. 0 The "map component" (<div tabindex="0">, which acts as control to both zoom and pan the map display) is missing name and role. Control to exit "Street view" (<div jsaction="closeControl.click">) is missing name and role. Control to reset the bearing (in "Street view") is missing name. Control to rotate the bearing counter-clockwise (in "Street view") is missing name. Control to rotate the bearing clockwise (in "Street view") is missing name. 2
MapKit JS (Apple Maps) API 0 The control to change/reset the bearing has a child node with the text character "N" to convey "North", it is presumably decorative but is not hidden from ATs. 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. 0 Some map labels (i.e. bodies of water, minor street labels) do not meet the minimum contrast ratio. 0 Map display is not pannable using a keyboard. 1 N/A (No single key shortcuts available.) 1 ChromeVox (no announcement) (no announcement) (no announcement) (no announcement) (no announcement) (no announcement) (The browser extension cannot read the Shadow DOM because shadowRoot.mode is closed.) NVDA "Change the map type button collapsed change the map type" "0 degrees 0 degrees slider N" "Zoom in button zoom in" "Zoom out button zoom out" "Legal button" 1 0 5/6 targets are too small. 0 1 0 The "Legal" link is incorrectly marked up as a button (role="button"), opens in new tab without warning. 0 Control to rotate/reset the bearing is missing role. (Note: there are two separate controls providing the same functionality, for clarification, this refers to <div tabindex="0"> and not the <input type="range" aria-label="{x} degrees"> control.) 4
TomTom Maps SDK for Web 0 Character "×" of the control to close the copyright message dialog is decorative but is not hidden from ATs. 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. The zoom controls' disabled state cannot be programmatically determined. 0 Some map labels (i.e. buildings) do not meet the minimum contrast ratio. 0 Control to open the copyright message dialog is not keyboard accessible. Control to close the copyright message dialog is not keyboard accessible. 1 1 1 ChromeVox "Map zoom in zoom out copyright 1992 to 2020 TomTom" "Map" "zoom in button" "zoom out button" NVDA "clickable map graphic zoom in button zoom in zoom out button zoom out clickable copyright 1992 to 2020 TomTom" (no announcement) "Zoom in button zoom in" "Zoom out button zoom out" 0 2 tab stops without focus indicators. 0 3/3 targets are too small. 0 0 1 0 The "map component" (<div tabindex="0">, which acts as a control to both zoom and pan the map display) is missing name and role. Control to open the copyright message dialog is missing role. Control to close the copyright message dialog is missing name and role. 4
OpenLayers API 0 Characters "+" and "" of the zoom controls are decorative but are not hidden from ATs. 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. The zoom controls' disabled state cannot be programmatically determined. 0 Some map labels (i.e. regional/districts) do not meet the minimum contrast ratio (uses OpenStreetMap tiles). 1 1 0 Both the arrow keys (used to pan the map display) as well as the keyboard shortcuts + and - (to zoom) can be activated despite unrelated components having focus. 1 ChromeVox "Plus button" "Minus button" "OpenStreetMap link list item-list with 1 items" NVDA "clickable Plus button zoom in" "button Zoom out" "list with 1 items OpenStreetMap link" 1 0 2/2 targets are too small. 0 1 0 The "OpenStreetMap" link opens in a new tab without warning. 0 Controls to zoom in and zoom out do not have proper names (while the titles "Zoom in" and "Zoom out" are appropriate, the child text nodes "+" and "" takes precedence in this case, according to the accessible name and description computation, and is announced as "button plus" and "button minus" in ChromeVox, for example). 5
Bing Maps Control API 1 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. Scale bars/rulers are missing contextual information (alternatively should be hidden from ATs). The "Reset orientation" control's disabled state cannot be programmatically determined. The "Locate me" control's pressed state cannot be programmatically determined. 0 Some map labels (i.e. bodies of water, district labels) do not meet the minimum contrast ratio. 0 Control to toggle area labels under "Bird's Eye view" is not keyboard accessible (has tabindex="-1"). 1 1 1 ChromeVox "Bing Maps interact to see more" "menu collapsed" "Locate me button" "Current level 1 zoom in button" "Bing Maps link" "Terms link" NVDA "clickable Bing Maps interact to see more" "Current map type: Road, view Aerial, Bird's Eye and more menu" "Locate me button locate me" "Current level 1 zoom in button zoom in" "Bing Maps link" "Terms link terms" 1 0 9/14 targets are too small. 0 1 0 Both the Bing Maps logo and the "Terms" link open in new windows without warning. 0 The "map component" (<div tabindex="0">, which acts as a control to both zoom and pan the map display) is missing role. Control to toggle area labels under "Bird's Eye view" is missing name. 6
OpenStreetMap embed 1 0 The zoom controls' disabled state cannot be programmatically determined. 0 Some map labels (i.e. regional/districts) do not meet the minimum contrast ratio (uses OpenStreetMap tiles). 1 1 1 1 ChromeVox "Zoom in zoom out report a problem copyright OpenStreetMap contributors" "Zoom in button" "Zoom out button" "Report a problem" "OpenStreetMap" NVDA "OpenStreetMap frame clickable zoom in zoom out report a problem copyright OpenStreetMap contributors zoom in button zoom in zoom out button zoom out clickable report a problem link copyright OpenStreetMap link contributors" "Zoom in button zoom in" "Zoom out button zoom out" "clickable Report a problem link" "OpenStreetMap link" 0 1 tab stop without focus indicator. 4 tab stops where focus indicators aren't persistently visible. 0 2/2 targets are too small. 0 1 0 The "Report a problem" and "OpenStreetMap" links open in a new tab without warning. 0 The "map component" (<div tabindex="0">, which acts as a control to both zoom and pan the map display) is missing name and role. 6
MapBox GL JS API 1 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. 1 0 Control to display attribution and feedback links is not keyboard accessible. [Github PR] 1 1 1 ChromeVox "Map" "MapBox logo link" NVDA "clickable map graphic" "MapBox logo link" 0 1 tab stop without focus indicator. [Github PR] 0 2/2 targets are too small. 0 1 0 Mapbox logo link opens in a new tab without warning. 0 The "map component" (<canvas tabindex="0">, which acts as a control to both zoom and pan the map display) is missing role. [Github PR] Control to display attribution and feedback links is missing name and role. [Github PR] 6
Leaflet JS API 1 0 The web map's semantic structure as a distinct piece of content cannot be programmatically determined. [Github issue] The zoom controls' disabled state cannot be programmatically determined. [Github issue] 0 Some map labels (i.e. regional/districts) do not meet the minimum contrast ratio (uses OpenStreetMap tiles). 1 1 1 1 ChromeVox "Zoom in zoom out Leaflet map data copyright OpenStreetMap contributors" "Zoom in button" "Zoom out button" "Leaflet link" "OpenStreetMap link" NVDA "clickable Zoom in zoom out Leaflet map data copyright OpenStreetMap contributors zoom in button zoom in zoom out button Zoom out clickable Leaflet link a JS library for interactive maps map data copyright OpenStreetMap link contributors" "Zoom in button zoom in" "Zoom out button zoom out" "clickable Leaflet link a JS library for interactive maps" "OpenStreetMap link" 0 1 tab stop without focus indicator. 4 tab stops where focus indicators aren't persistently visible. [Github issue] 0 2/2 targets are too small. 0 1 1 0 The "map component" (<div tabindex="0">, which acts as a control to both zoom and pan the map display) is missing name and role. [Github issue] 7
MapBox Studio embed 1 0 The "Zoom out" control's disabled state cannot be programmatically determined. [Github PR] 1 0 Control to display attribution and feedback links is not keyboard accessible. [Github PR] 1 1 1 ChromeVox "Map" "Search edit text" "Zoom in button" "Zoom out button" "Reset bearing to north button" "MapBox logo link" NVDA "MapBox Studio frame clickable map graphic" "Search edit blank" "Zoom in button zoom in" "Zoom out button zoom out" "Reset bearing to north button reset bearing to north" "MapBox logo link" 0 1 tab stop without focus indicator. [Github PR] 0 7/7 targets are too small. 1 1 0 Mapbox logo link opens in a new tab without warning. 0 The "map component" (<canvas tabindex="0">, which acts as a control to both zoom and pan the map display) is missing role. [Github PR] Control to display attribution and feedback links is missing name and role. [Github PR] 7
Bing Maps embed 1 0 Scale bars/rulers are missing contextual information (alternatively should be hidden from ATs). 0 Some map labels (i.e. bodies of water, district labels) do not meet the minimum contrast ratio. 1 1 1 1 ChromeVox "Bing Maps interact to see more" "Current level 1 zoom in button" "Bing Maps link" "Terms link" NVDA "Bing Maps frame clickable Bing Maps interact to see more" "Current level 1 zoom in button zoom in" "Bing Maps link" "Terms link terms" 1 0 3/3 targets are too small. 1 1 0 Both the Bing Maps logo and the "Terms" link open in new windows without warning. 0 The "map component" (<div tabindex="0">, which acts as a control to both zoom and pan the map display) is missing role. 8
Can't render this file because it contains an unexpected character in line 2 and column 41.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 751 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 111 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 558 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 738 KiB

View file

@ -1,5 +0,0 @@
,OpenLayers,OpenLayers+MB,OpenLayers+React,Leaflet,Mapbox,Mapbox React
Style Loaded,300.22,1195.81,331.85,4460.74,225.97,577.28
Map Idle,1039.56,1195.63,1184.71,126.85,3837.53,2146.08
DOM Interactive,126.88,137.59,193.75,131.33,154.01,262.01
DOM complete,132.70,138.13,193.89,240.13,176.06,353.36
1 OpenLayers OpenLayers+MB OpenLayers+React Leaflet Mapbox Mapbox React
2 Style Loaded 300.22 1195.81 331.85 4460.74 225.97 577.28
3 Map Idle 1039.56 1195.63 1184.71 126.85 3837.53 2146.08
4 DOM Interactive 126.88 137.59 193.75 131.33 154.01 262.01
5 DOM complete 132.70 138.13 193.89 240.13 176.06 353.36

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 MiB

View file

@ -1,5 +0,0 @@
,OpenLayers,OpenLayers+MB,OpenLayers+React,Leaflet,Mapbox,Mapbox React
Style Loaded,408.89,383.19,439.83,1782.45,147.41,0.00
Map Idle,207.23,1263.27,1318.54,120.00,1970.86,1659.42
DOM Interactive,146.15,108.78,171.48,124.38,143.74,269.40
DOM complete,146.45,109.33,171.69,160.65,166.53,374.69
1 OpenLayers OpenLayers+MB OpenLayers+React Leaflet Mapbox Mapbox React
2 Style Loaded 408.89 383.19 439.83 1782.45 147.41 0.00
3 Map Idle 207.23 1263.27 1318.54 120.00 1970.86 1659.42
4 DOM Interactive 146.15 108.78 171.48 124.38 143.74 269.40
5 DOM complete 146.45 109.33 171.69 160.65 166.53 374.69

Binary file not shown.

Before

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

View file

@ -1,182 +0,0 @@
# Mapping Visualization Library
- Status: approved
- Deciders: Lucas Brown, Shelby Switzer, Nat Hillard
- Date: 2021-04-21
- Tags: frontend
Technical Story: [#7](https://github.com/usds/environmental-justice/issues/7)
## Context and Problem Statement
In order to visualize the metrics of the Justice 40 initiative, we need a client-side mapping solution. This solution should should reflect the values of our initiative, and it should reflect and respond to the diverse needs of our users.
**Team Values**: The solution should be open-source, modular, and adopt modern best practices.
**User needs**: The solution should be performant, accessible, internationalized/localized, and minimize data usage.
We provide more detail on these factors below.
## Decision Drivers
### Team Values
- **Open source** : Our technology choices should "default to open" as detailed in the [Digital Services Playbook](https://playbook.cio.gov/#play13), in order to encourage community contribution, minimize vendor lock-in, and lower the barrier to entry to participate in the development of the tool. Ideally such a solution should be be true [Free and Open Source Software](https://en.wikipedia.org/wiki/Free_and_open-source_software), licensed for use, copy, and modification as well as source code-visible.
- **Ease of implementation** : In the name of welcoming a large body of open-source contributors, our implementation should not require too significant insider knowledge of mapping or GIS systems. At the same time, more advanced functionality should be familiar to those who do have such backgrounds, to enable them to contribute expertise where warranted. Additionally, it should be possible to get an environment up and running locally without a necessary API key or other paid plan.
- **Modular** : we should minimize vendor lock-in and allow for mixing and matching solutions from different providers -- from server-vended tiling solutions to tile storage and basemaps -- in order to optimize user experience and find the best tool for the job at all layers of the stack.
- **Modern** : the library should allow for its work to be done entirely in the browser; it should provide the ability to load on demand (in binary, protobuf [format](https://github.com/mapbox/vector-tile-spec)) and visualize so-called [vector tiles](https://en.wikipedia.org/wiki/Vector_tiles) to make use of the rendering power of [WebGL](https://www.khronos.org/webgl/); and it should provide for client-side dynamic map styling according to relevant [standards](https://docs.mapbox.com/mapbox-gl-js/style-spec/) as opposed to just server-side pre-rendering of raster tiles.
### User Needs
- **Performant** : Library should be performant on a range of device types, enabling fast load times and ease of navigation in a highly-complex rendering environment where there may be hundreds of thousands of features.
- **Accessible** : the implementation we build with this library must be compliant according to [section 508](https://www.section508.gov/) of the Rehabilitation Act [(29 U.S.C. § 794d)](https://www.govinfo.gov/content/pkg/USCODE-2011-title29/html/USCODE-2011-title29-chap16-subchapV-sec794d.htm), as well as [Section 501](https://www.eeoc.gov/statutes/rehabilitation-act-1973) guidance regarding Reasonable Accommodation per US federal government legal requirements. Ideally it should adopt [Web Content Accessibility Guidelines](https://www.w3.org/WAI/standards-guidelines/wcag/) to at least the AA level. The library our solution builds upon should provide some of this out of the box.
- **Internationalized/Localized** : Nearly [22%](https://data.census.gov/cedsci/table?q=B16001&hidePreview=false&tid=ACSDT1Y2017.B16001&vintage=2018) of the US population speaks a language other than English at home. Our mapping solution should be familiar and easy to use by this population.
- **Minimizes network usage** : Where possible, we should seek to minimize network usage to ensure lower data usage costs and faster load times.
## Considered Options
1. [Mapbox GL JS](https://github.com/mapbox/mapbox-gl-js) - v2.2.0 (3/25/21)
2. [MapLibre GL JS](https://github.com/maplibre/maplibre-gl-js) - v1.14.0 (3/24/21)
3. [Leaflet](https://github.com/Leaflet/Leaflet) - v1.7.1 (9/4/20) + [Leaflet.VectorGrid](https://github.com/Leaflet/Leaflet.VectorGrid) - v1.3.0 (2017)
4. [OpenLayers](https://github.com/openlayers/openlayers) - v6.5.0 (12/27/20)
5. [ArcGIS API for Javascript](https://developers.arcgis.com/javascript/latest/) - v4.18 (12/2020)
## Not Considered Options
- Non-ArcGIS-JS-API Esri [products](https://www.esri.com/en-us/arcgis/products/index) - The focus of this investigation is web APIs that can be incorporated into a website and rendered as part of a cloud-based, modular web-development flow. Additionally, these are not open source.
- [Google Maps Platform](https://developers.google.com/maps)/[Bing Maps APIs](https://www.microsoft.com/en-us/maps/choose-your-bing-maps-api) - These mapping are also closed source and proprietary, and do not allow for significant customization or visualization of the sort we would need for this product
- [d3.js](https://d3js.org/) - though impressive in its offerings and performance, d3 appears more oriented to more general-purpose infographic use than a mapping-specific product requiring significant GIS-adjacent features. We may consider aspects of D3 for particular chart renderings.
## Decision Outcome
**OpenLayers** ([Source](https://github.com/openlayers/openlayers) - v6.5.0 (12/27/20)). This library is completely free and open-source, modular, and modern. As a modern, well-typed and well-documented javascript library, it is approachable, yet offers a full-featured and familiar interface as well for those with a background in GIS systems. As it is free to use, anyone could clone our source and run our project locally without payment or registration. Additionally, it has relatively good performance for large feature sets, despite not using WebGL for rendering.
### Positive Consequences
- **Licensing** : OpenLayers is BSD 2-Clause [licensed](https://github.com/openlayers/openlayers/blob/main/LICENSE.md)
- **More fully featured** : OpenLayers has a number of GIS features that other web-based tools do not. A somewhat older [analysis](https://link.springer.com/article/10.1007/s10109-017-0248-z) done in 2017 concluded that when compared to a set of considered features, OpenLayers 3 had a relatively large number of supported overall GIS features:
![GIS Feature set](./0002-files/GIS_Features.png)
- **Popularity** : OpenLayers is second only to Leaflet in the number of Github [stars](https://github.com/openlayers/openlayers/stargazers) it has received, close to 8000
- **Performance** :
- The below chart comes from an September 2020 [study](https://doi.org/10.3390/ijgi9100563). The purpose of this study was to compare OpenLayers to Mapbox-GL-JS and Leaflet (both raster and vector tile variants) as the potential basis for a Life Quality Index for 55,000+ census radius jurisdictions in Argentina.
![Execution Time](./0002-files/ExecutionTime.png) ([Source](https://doi.org/10.3390/ijgi9100563))
In this chart, the two letters following the library name are for basemap layer and feature layer. Further, "R" is "raster" and "V" is vector, and lower numbers indicate faster load times. "OpenLayersRR" and "OpenLayersRV", (signifying a raster base layer and vector feature layer), performed quite well across all device types compared to other libraries.
- We also performed local testing using puppeteer and web performance APIs, tested against a choropleth map of the cenus block groups, which represents a likely usecase for us. The results were as follows:
![Choropleth Map Performance](./0002-files/ChoroplethMapPerformance.png) ([Source](./0002-files/Maryland.csv))
Testing and results can be found on the `client_perf` [branch](https://github.com/usds/justice40-tool/tree/client_perf) in this repository.
- Finally, we tested these mapping solutions against a large dataset with many features, a map of national highways, at a lower zoom level. The tiles were still loaded on-demand, but the greater detail and number of features meant a more difficult render. Trends seemed similar to the above, with the exception of Leaflet:
![Highways Map Performance](./0002-files/HighwayMapPerformance.png) ([Source](./0002-files/Highways.csv))
Notes:
- OL+MB did not file the `tiledidload` event and thus there was not a separate measure for style loaded
- Apparent performance was different from measured/reported overall, particularly when it comes to zoom performance. This is an area to dig into further and measured at a later time to understand better.
- **Data Usage** : The same study above also analyzed the amount of data usage for each of the libraries under investigation, and the result was the below chart (Lower values are better). OpenLayers overall performed quite well
![Data Usage](./0002-files/NetworkTraffic.png) ([Source](https://doi.org/10.3390/ijgi9100563))
### Negative Consequences
- **Learning Curve** : A [recent survey](https://pea.lib.pte.hu/bitstream/handle/pea/23611/farkas-gabor-phd-2020.pdf?sequence=1) ranking various mapping libraries ranked OpenLayers as "Hard" in a scale from "Basic" to "Very Hard", and furthermore calculated "Approximate Learning Curve for Javascript", by which it was on the harder end as well (more detail in paper):
![Difficulty Ranking](./0002-files/DifficultyRanking.png) ([Source](https://pea.lib.pte.hu/bitstream/handle/pea/23611/farkas-gabor-phd-2020.pdf?sequence=1))
This is part of the tradeoff in the library's large feature set. Anecdotally, however, for the purpose of a simple choropleth map, implementation felt similar to other libraries.
- **Modern Stack** : OpenLayers requires a [plugin](https://github.com/openlayers/ol-mapbox-style) for the combination of MVT vector tiles and Mapbox GL Styles, though it does support it.
- **Rendering** : According to [geoapify](https://www.geoapify.com/mapbox-gl-new-license-and-6-free-alternatives), it has 'slightly less "polished" rendering quality and performance' compared to Mapbox/MapLibre GL. This is partly related to the fact that it does not yet support using WebGL for rendering anything more than points ([source](https://openlayers.org/workshop/en/webgl/points.html)).
## Observations
- **Accessibility**
- Accessibility for web mapping technology in general is [not](https://wcag-maps.nicchan.me/) [good](https://sparkgeo.com/blog/the-accessibility-of-web-maps/). As of now, the default 2D visualizations in and of themselves do not provide explicit 508 compliance, and there is no official accessibilty [statement](https://www.w3.org/WAI/planning/statements/) for OpenLayers.
- According to a recent exhaustive WCAG 2.1 [evaluation](https://github.com/Malvoz/web-maps-wcag-evaluation/) that compared several map providers, though almost all web maps performed [poorly](https://wcag-maps.nicchan.me/), OpenLayers came out in the middle of the pack:
![Accessibility](./0002-files/WCAG2.1SuccessCount.png) (Source: original [here](./0002-files/AccessibilityComparison.tsv), based on data [here](https://github.com/Malvoz/web-maps-wcag-evaluation/))
This chart counts the number of successful WCAG 2.1 criteria from the above-linked study.
- Mitigations:
- According to at least one accessibility [expert](http://stackoverflow.com/questions/15659051/google-maps-508-accessibility-without-styles/16060809#16060809), it is sufficient for compliance reasons to provide an alternative means of viewing the same information provided by the map, perhaps in a table or other format. This is a common solution to this problem.
- Though the library itself does not provide an accessibility guarantee, many of the 508 and WCAG standards are a matter of how you _use_ a particular library - e.g. ensuring proper color contrast, providing alternative markers not based on coloration, and providing text highlighting. We can guarantee that these are in place and ensure an accessibility audit of our featureset before release alongside user testing with low-vision and visually-impaired users directly.
## Pros and Cons of the Other Options
### Mapbox-GL JS
[Source](https://docs.mapbox.com/mapbox-gl-js/api/) - v2.2.0 (3/29/21)
#### Mapbox-GL JS Pros
- **Performance** : As can be seen in the above charts, Mapbox performed quite well in performance tests, in some instances performing the best of the examined options.
- **Data Usage**: In the data usage chart above, MapboxRV and MapboxVV also do quite well in the category of data usage.
- **Modern stack**: Mapbox supports WebGL rendering, vector tiles, and standardized tile styling
- **Modularity**: Mapbox interoperates with various basemap providers, and thanks to the open-sourcing of both its [vector tile format](https://docs.mapbox.com/vector-tiles/specification/) and its [styling language](https://docs.mapbox.com/mapbox-gl-js/style-spec/), there are a [number](https://github.com/mapbox/awesome-vector-tiles) of tools, libraries, and frameworks that easily interoperate with this solution.]
- **(Relative) Accessibility** : Mapbox-GL-JS came out 4th out of 12 in the above accessibility analysis. Mapbox also released several [patches](https://github.com/mapbox/mapbox-gl-js/pulls?q=is%3Apr++accessibility+) recently to address the issues that were found and are actively working on this.
- **Localization** : Through the mapbox-gl-language [plugin](https://github.com/mapbox/mapbox-gl-language/), it is possible to dynamically change the language of mapbox-provided basemaps to other languages. Additionally, they have a plugin for displaying [RTL languages](https://github.com/mapbox/mapbox-gl-rtl-text), and the [ability](https://docs.mapbox.com/mapbox-gl-js/style-spec/expressions/#types-number-format) to format numbers according to locale conventions.
- **Popularity** : According to [NPM Trends](https://www.npmtrends.com/mapbox-gl-vs-leaflet-vs-ol-vs-arcgis-js-api-vs-maplibre-gl) Mapbox-GL is the most downloaded package among those considered. More info [here](https://www.geoapify.com/map-libraries-comparison-leaflet-vs-mapbox-gl-vs-openlayers-trends-and-statistics)
![Download Stats](./0002-files/MapDownloadCount.png)
#### Mapbox-GL JS Cons
- **Licensing** : Mapbox's December 2020 [announcement](https://github.com/mapbox/mapbox-gl-js/releases/tag/v2.0.0) of version 2.0 of their software changed ther license to proprietary and changed their pricing scheme to cover tiles loaded from outside of their service. This decision was met with [some criticism](https://joemorrison.medium.com/death-of-an-open-source-business-model-62bc227a7e9b) in the open-source mapping community.
- **Library Size** : Mapbox-GL is one of the larger libraries considered as far as filesize, coming in at 214.1 KB ([Source](https://www.npmtrends.com/mapbox-gl-vs-leaflet-vs-ol-vs-arcgis-js-api-vs-maplibre-gl))
- **Pricing** : As of version 2.0.0, Mapbox [charges](https://www.mapbox.com/pricing/#maploads) per "map load", defined as "every time Mapbox GL JS initializes on a webpage or in a web app". Up to 50,000 loads per month are free, and beyond this pricing varies. Users are likely to do a number of loads per sitting, however, so this is something we will need to consider, even in a relatively low-traffic tool.
### MapLibre-GL-JS
[Source](https://github.com/maplibre/maplibre-gl-js) - v1.14.0 (3/24/21)
MapLibre is a fork of Mapbox 1.13.0 that preserves the older BSD 3-clause license.
#### MapLibre-GL-JS Pros
- **Everything above** : Being a fork of Mapbox GL JS (though an earlier version), MapLibre has all of the benefits of Mapbox called out above
- **Licensing** : Returns to 3-Clause BSD [License](https://github.com/maplibre/maplibre-gl-js/blob/main/LICENSE.txt) means true FOSS licensing, as well as free operation
#### MapLibre-GL-JS Cons
- **Maturity** : This library is still quite young, having been formed only mid-December last year
- **Licensing Complications** : There may be licensing or legal challenges from Mapbox around the use of this tool
- **Component-based Wrappers** : Though supported by [react-map-gl](https://github.com/visgl/react-map-gl), there is not yet as full-featured support for MapLibre-GL in other component-based wrapper libraries
- **Backwards Compatibility** : There is an [issue](https://github.com/maplibre/maplibre-gl-js/pull/21) open now discussing the possibility of breaking backwards compatibility as part of a MapLibre 2.0 release. In practice this would mean we would not be able to use layers created within Mapbox in MapLibre tooling.
- **New Features** : There is not yet clarity on how and when this library would be updated and what its upcoming feature roadmap looks like
### Leaflet
[Source](https://github.com/Leaflet/Leaflet) - v1.7.1 (9/4/20) + [Leaflet.VectorGrid](https://github.com/Leaflet/Leaflet.VectorGrid) - v1.3.0 (2017)
Leaflet is a popular open-source mapping visualization library that has been around since 2010.
#### Leaflet Pros
- **Popularity** : Leaflet has by far the largest number of [stars](https://github.com/Leaflet/Leaflet/stargazers) on Gitbub (30,500 at the time of writing)
- **Modularity** : Leaflet has a [number](https://leafletjs.com/plugins.html) of plugins that augment its functionality, including [Leaflet.VectorGrid](https://github.com/Leaflet/Leaflet.VectorGrid) which enables Vector Tiles
- **License** : Leaflet is BSD 2-Clause [licensed](https://github.com/Leaflet/Leaflet/blob/master/LICENSE)
#### Leaflet Cons
- **Performance** : As seen above, Leaflet performance is quite poor across device types, relative to other libraries, even with the vector plugin
- **Modern Stack** : The lack of out-of-the-box vector tile support and the necessity of another library opens the possibility for version requirement mismatches and security vulnerabilities. Additionally, the last update to the VectorGrid plugin was a [year ago](https://github.com/Leaflet/Leaflet.VectorGrid/commits/master).
### ArcGIS API for Javascript
[Source](https://developers.arcgis.com/javascript/latest/) - v4.18 (12/2020)
#### ArcGIS API for Javascript Pros
- **Accessibility** : ArcGIS provides regularly-updated accessibility [conformance reports](https://www.esri.com/en-us/legal/accessibility/conformance-reports) detailing their products' conformance with 508 standards.
- Note: These audits are not all up to date - the ArcGIS online evaluation was last completed in September 2018
- Anecdotally, screen readers still struggle to read content without annotations - the onus is still on the map creator to ensure proper compliance.
- **Internationalization/Localization** : the API has its own `intl` module, described [here](https://developers.arcgis.com/javascript/latest/api-reference/esri-intl.html)
- Note: The default ES6 `Intl` module appears to do all of the things described above without need for further modificaiton
- **Name recognition** : Esri in general holds "approximately 43% of the global market share and estimated annual revenues of approximately $1.1 Billion, from roughly 300,000 customers" ([source](https://digital.hbs.edu/platform-digit/submission/esri-and-arcgis/])). Relatedly, Esri tools interoperate well with other Esri tools, and their ecosystem is large.
- Note: according to [one analysis](https://www.datanyze.com/market-share/mapping-and-gis--121) specifically the ArcGIS Web API holds only 0.48% of the marketshare overall.
- **Modularity** : As seen in ArcGIS [documentation](https://developers.arcgis.com/documentation/), Esri interoperates with Leaflet (through a custom fork), Mapbox GL JS, and OpenLayers.
#### ArcGIS API for Javascript Cons
- **License** : Most importantly, though Esri maintains a [handful](https://www.esri.com/en-us/arcgis/open-vision/initiatives/open-source) of open-source tools, ArcGIS API for JS itself, as well as many other tools within the Esri / ArcGIS portfolio, is [closed source](https://github.com/Esri/arcgis-js-api/blob/master/copyright.txt) and proprietary, hosting only minified versions of their software and not encouraging community contribution or feedback beyond forum contributions.
- **Complexity** : The feature set comes with the tradeoff of greater complexity in implementation for ArcGIS as well. Additionally, many more advanced features of the ArcGIS API require you to utilize the Dojo framework and/or Dijit components (see examples [here](https://developers.arcgis.com/javascript/latest/sample-code/widgets-frameworks-react/)), a requirement not held by other frameworks.
- **Modern** : ArcGIS-JS-API only recently got support for ES Modules in version 4.19 ([source](https://developers.arcgis.com/javascript/latest/release-notes/)). Additionally, many examples from earlier versions are still common, including in the ArcGIS API for JS documentation [itself](https://developers.arcgis.com/javascript/latest/get-started/), which still uses HTML includes. There are recently some newer [examples](https://github.com/Esri/jsapi-resources/tree/master/esm-samples) in github, but they also seem to have been recently added.
## Appendix A: Other comparisons
There are a handful of other similar comparisons out there, here are some references:
- Geoapify comparison ([Source](https://www.geoapify.com/mapbox-gl-new-license-and-6-free-alternatives))
![Geoapify Comparison](./0002-files/GeoapifyComparison.png)
- Flatlogic [Comparison](https://flatlogic.com/blog/top-mapping-and-maps-api/)
## Appendix B: Further Reading
- [Malvoz's WCAG 2.1 Summary](https://github.com/Malvoz/web-maps-wcag-evaluation/blob/master/README.md)

View file

@ -1,105 +0,0 @@
# ADR Process
- Status: Proposed
- Deciders: Justice40 Team
- Date: 2021-05-10
- Tags: Process
Technical Story: N/A
## Context and Problem Statement
We should document the process of creating an ADR to provide clarity to committers of this project.
## Decision Drivers
- **Clarity** - Identify historical reasoning behind decision-making
- **Transparency** - Make decisions in the open
- **Currency** - ADRs reflect the current state of the world, but can be superseded by later decisions. The highest-numbered ADR reflects the most recent decision around a given topic.
- **Expediency** - We need good decisions, but good good software ships frequently. To balance these two factors, the ADR ensures we move forward in an informed way, but provides a way to self-correct as the world changes.
## Considered Options
- ADRs
- Informal Conversations
- Unilateral decision-making
## Decision Outcome
See Below for a way to use ADRs to make decisions on the project.
### What is an ADR?
An Architecture Decision Record is a text file, with a particular format, and an associated discussion. It is numbered for easy reference, and it has a general format.
### When Should I Create an ADR?
As outlined by Michael Nygard, ADRs represent "architecturally significant" decisions.
The subject of an ADR should affect one of the following areas:
- Structure
- Dependencies
- Interfaces
- Construction techniques
Small changes don't need one, but for larger changes that would introduce a new dependency or affect our overall system architecture, we recommend opening one.
### Process
Please see the below diagram:
![ADR Process](./0003-files/adr_process-mmd.svg)
1. **Start**: To create a new ADR, copy `template.md` into a new file in this directory, named `XXXX-name-of-issue.md` where the `XXXX` part is incremented from the previous ADR.
2. **Draft**: While your ADR is still being written, set the status to `draft`
3. **Proposed**: Fill out all fields, set status to `proposed`, and open a pull-request.
1. We recommend [Mermaid](https://mermaid-js.github.io) for diagrams! Create a file with a `.mmd` extension and reference `filename-mmd.svg` in your docs, and Github Actions will automatically create the svg fore you! Preview diagrams with the Mermaid [Live Editor](https://mermaid-js.github.io/mermaid-live-editor/).
2. To keep things organized, create a folder with the name `XXXX-files` and put supporting ADR material there.
4. **Email** Send an email to [the justice40-open-source Google Group](mailto:justice40-open-source@googlegroups.com) and link to the Pull Request created in (2). Set a deadline to solicit feedback, at most one week away.
5. **Consensus** In the event of consensus within the expressed timeline, feel free to merge.
6. **Concerns** If there are concerns raised, ensure that those that disagree understand the reasoning behind the decision and address concerns with specific mitigation steps. Request confirmation that concerns have been addressed. If so, merge!
7. **Discuss** In the event that disagreement persists, discuss the decision with the group in the next biweekly sync. Take a vote on next steps (opt-outs are OK!), and move forward.
8. **Merge**
1. If the proposal is **approved** by this vote, set its status to `approved`, and merge the change.
2. If it is **rejected**, set its status as `rejected`, and merge anyway for future reference!
9. **Update Other Issues** If people agree in discussion that this issue supersedes another, set the status of the other issue to `superseded`
10. **Close** Close the issue
### Positive Consequences
- Clarity on decisions
- A history of all decisions made on the project
- Inclusion of community voices in discussion of important topics
### Negative Consequences
- Folks have to follow a more involved process
- Might slow things down in the event of major disagreements
## Pros and Cons of Other Options
### Informal Conversations
- Pros: Easier, Faster
- Cons: Can leave people out
### Unilateral Decision Making
- Pros: The fastest option
- Cons: Many - less participation and inclusion, fewer perspectives.
## Links <!-- optional -->
- Documentation on the MADR [template](https://github.com/adr/madr)
- Blog [post](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions.html) introducing ADRs

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 17 KiB

View file

@ -1,12 +0,0 @@
graph TB
Draft -- ADR Pull Request Written --> Proposed
Proposed -- Emailed --> C{Consensus}
C --> |Yes| Merged
C --> |No| A{Concerns Addressed?}
A --> |Yes| Merged
A --> |No| Discussion
Discussion --> O{Voting Outcome}
O --> |Accepted| Merged
O --> |Rejected| Merged
Merged --> U[Update other Issues]
U-->Close

View file

@ -1,142 +0,0 @@
# Client-side Framework
- Status: draft
- Deciders: Justice40 team
- Date: 2021-05-19
- Tags: front-end, client
Technical Story: https://github.com/usds/justice40-tool/issues/44
## Context and Problem Statement
We need to decide what framework to use for our front end client.
## Decision Drivers <!-- optional -->
- **Default to open** - Choose open source technology in our stack.
- **Team skills and capacity** - Most of the team has React experience.
- **Ease of implementation** - We're looking for significant community support and a shallow learning curve.
- **Skills of our community** - Our open source community as well as potential collaborators in federal government are likely to have experience with Python and JavaScript, so we're not strongly considering options outside of these ecosystems.
- **Can support following features:**
- USWDS integration
- Content management by nontechnical team member, such as with Markdown files or integration with a headless CMS
- Solid accessibility support
- Offline access / low bandwidth support
- Can generate static site
- Ease of use with Open Layers
- Localization support
## Considered Options
- Jekyll
- Gatsby
- Next.js
- Gridsome
- Eleventy
- Static HTML + JS site
- Create-react-app
## Decision Outcome
Chosen option: Gatsby, because it seems to hit the balance between being simple and static-site focused and having capabilities for scaling up features and eventually evolving to a full stack application should we need it.
### Positive Consequences <!-- optional -->
- Very large plugin [ecosystem](https://www.gatsbyjs.com/plugins)
- Batteries included philosophy helps in rendering static pages, routing, styling, markdown rendering
- Easy CMS support
- Straightforward to learn / pick up
- Fairly good [documentation](https://www.gatsbyjs.com/docs)
### Negative Consequences <!-- optional -->
- Test development environment ran out of memory. We debugged this further and could not replicate the problem on a new machine, but the original experience was annoying to work around and this [article](https://support.gatsbyjs.com/hc/en-us/articles/360053096273-Why-did-I-hit-Out-of-Memory-errors-) suggests it could be a more widespread problem.
- Local builds and refreshes feel slow compared to other frameworks on this list
- Seems a little more geared toward the blog usecase
## Pros and Cons of the Options
### Jekyll
Pros:
- Easy to use
- Solid community support
- Team is familiar with it
- Can be hosted easily as static site on Github Pages or other platform
Cons:
- Limited in terms of support for more advanced interactive features / JS components: based on team's experience, we will run up to the limitations pretty quickly
- Not scalable past a static site option if we need that
- We might need non-static solution in the future / dynamic pages - how
### Next
Pros:
- By far the most commonly framework in this list - 1.4MM [total downloads](https://www.npmtrends.com/gatsby-vs-next-vs-nuxt-vs-vuepress-vs-create-react-app-vs-gridsome) as of May 2, 2021 . Gatsby, the second most-downloaded, has ~470,000
- Used by a number of well-known [companies and brands](https://nextjs.org/showcase)
- Flexible
- Mature tooling like `create-next-app`
- Straightforward, file-based routing
- Image optimization
Cons:
- A bit awkward to have to opt out of SSR, navigate distinctions between static generation (with and without data), server-side rendering, and to know when to pre-render
- Compared to other frameworks here, fewer batteries included -- this is almost a pro but it means we'd have to reimplement things like markdown rendering
- Not having / wanting a server to work with, we're missing out on some of Next.js's functionality to start with
- A steeper learning curve than others on this list
### Gridsome
Pros:
- Overall seems to share many of the benefits of Gatsby
- CMS integration
- GraphQL + data source integration very similar to Gatsby's
- Good [documentation](https://gridsome.org/docs/)
- Large [plugin](https://gridsome.org/plugins/) library
- Straightforward, file-structure-based routing
- Asset optimization, progressive image loading
Cons:
- Vue support is limited to v2, no support yet for v3 (released Sept 2020) (though notably a lot of libraries don't support Vue3)
- Relatively unknown compared to other Vue-based frameworks: 7200 stars, compared to VuePress 18200, Nuxt.js's 33000
- There is a (really cool!) USWDS wrapper [library](https://github.com/usds/uswds-vue) -- and it's even supported by Mike Pritchard at USDS! -- providing Vue support, but it's still relatively new (Sep 2020)
### Eleventy
Pros:
- Used elsewhere in USDS (PRIME ReportStream)
- Simple and straightforward
- Seems to be like Jekyll but for JavaScript
Cons:
- Static-site only: Would be difficult to build out a more fully featured app later from this base
- Doesn't appear to offer us much in terms of speeding up the build
### Static HTML + JS site
Pros:
- Simple: We wouldn't have to deal with idiosyncracies/conventions of a particular framework or work through dependency conflicts/issues
Cons:
- We would have to build a lot more ourselves, including support for non-technical content management
- Not easily scalable past a static site (would have to do a lot of work to add a framework or functionality later)
### Create-react-app
Pros:
- Standard app for getting started in React (and we are leaning towards React as a team)
- Like with the static site, there's less out of the box but more customization possible and potentially less to get in our way
Cons:
- We'd have to figure out things like static site generation and CMS integration on our own
- Along those lines, doesn't appear to offer us much in terms of speeding up the build
## Links <!-- optional -->

View file

@ -1,57 +0,0 @@
# Hosting
- Status: Proposed
- Deciders: Justice40 Team
- Date: 2021-05-18
- Tags: Hosting, Server, Geoplatform
Technical Story: https://github.com/usds/justice40-tool/issues/18 + https://github.com/usds/justice40-tool/issues/36
## Context and Problem Statement
We need a host for our data pipeline and front end website/app.
## Decision Drivers
- **Speed of launch** - We have a mandate to launch an initial version of our tool by July 27, and we want to get an informational site up much sooner.
- **Support for continuous integration and delivery** - We need our hosting provider to support a modern software development lifecycle that includes continuous integration and delivery, such as integration with our Github repository and CI/CD tool such as Github Actions.
- **Ease of implementation** - Ideally we can choose a platform that some of the team has experience with, that doesn't have a steep learning curve, and/or that has good support.
- **Commitment to open source and process** - Any code used for servers, data processing, or front end hosting must be able to be open source, so there cannot be limitations with regard to where/how code is hosted and shared.
## Considered Options
- Geoplatform
- Cloud.gov
## Decision Outcome
We will use Geoplatform.gov for hosting our data pipeline, tile server, and front end client. They have a set of shared service offerings that will enable us to have an open data pipeline from data source to tile API, enabling contributions to data processing at any point as well as enabling data access for users at any point (e.g. whether a user wants to access raw data or GeoJSON or tile format). See this ![diagram](architecture.mmd.svg) that illustrates the proposed system architecture and hosting.
### Positive Consequences
- No need to stand up our own servers for data processing or vending tiles
- No need to find separate static site hosting for our front end
- Help advance shared geo services for government
- Code for data transformations can live on an open Github repo and be collaborated on by the community
### Negative Consequences
- Possible delays or additional work: Some features and offerings we may want to use are still a work in progress on Geoplatform's side, so we may have to help build these out or wait for their release.
## Pros and Cons of Other Options
### Cloud.gov
Pros:
- Control over software development lifecycle including open source code and CI/CD setup
- Supports CI/CD
- Supports a shared service in government
- The team has experience with Cloud.gov
Cons:
- We would not be able to launch our website or backend on our expected timeline
- Would require a lot more build on our part for the data pipeline and tile server
- Would not have the flexibility to easily run serverless functions if we needed to
## Links <!-- optional -->

View file

@ -1,3 +0,0 @@
# Decisions
This folder is for gathering [Architectural Decision Records](https://cognitect.com/blog/2011/11/15/documenting-architecture-decisions.html), documenting decisions we have made for future reference. Read the above linked-to post to understand background and motivations, or read issue `0001-record-architecture-decisions.md`!

View file

@ -1,73 +0,0 @@
# [short title of solved problem and solution]
- Status: [draft | proposed | rejected | accepted | deprecated | … | superseded by [xxx](yyyymmdd-xxx.md)] <!-- optional -->
- Deciders: [list everyone involved in the decision] <!-- optional -->
- Date: [YYYY-MM-DD when the decision was last updated] <!-- optional. To customize the ordering without relying on Git creation dates and filenames -->
- Tags: [space and/or comma separated list of tags] <!-- optional -->
Technical Story: [description | ticket/issue URL] <!-- optional -->
## Context and Problem Statement
[Describe the context and problem statement, e.g., in free form using two to three sentences. You may want to articulate the problem in form of a question.]
## Decision Drivers <!-- optional -->
- [driver 1, e.g., a force, facing concern, …]
- [driver 2, e.g., a force, facing concern, …]
- … <!-- numbers of drivers can vary -->
## Considered Options
- [option 1]
- [option 2]
- [option 3]
- … <!-- numbers of options can vary -->
## Decision Outcome
Chosen option: "[option 1]", because [justification. e.g., only option, which meets k.o. criterion decision driver | which resolves force force | … | comes out best (see below)].
### Positive Consequences <!-- optional -->
- [e.g., improvement of quality attribute satisfaction, follow-up decisions required, …]
- …
### Negative Consequences <!-- optional -->
- [e.g., compromising quality attribute, follow-up decisions required, …]
- …
## Pros and Cons of the Options <!-- optional -->
### [option 1]
[example | description | pointer to more information | …] <!-- optional -->
- Good, because [argument a]
- Good, because [argument b]
- Bad, because [argument c]
- … <!-- numbers of pros and cons can vary -->
### [option 2]
[example | description | pointer to more information | …] <!-- optional -->
- Good, because [argument a]
- Good, because [argument b]
- Bad, because [argument c]
- … <!-- numbers of pros and cons can vary -->
### [option 3]
[example | description | pointer to more information | …] <!-- optional -->
- Good, because [argument a]
- Good, because [argument b]
- Bad, because [argument c]
- … <!-- numbers of pros and cons can vary -->
## Links <!-- optional -->
- [Link type](link to adr) <!-- example: Refined by [xxx](yyyymmdd-xxx.md) -->
- … <!-- numbers of links can vary -->

1
en/404.html Normal file

File diff suppressed because one or more lines are too long

1
en/404/index.html Normal file

File diff suppressed because one or more lines are too long

1
en/index.html Normal file

File diff suppressed because one or more lines are too long

1
es/404.html Normal file

File diff suppressed because one or more lines are too long

1
es/404/index.html Normal file

File diff suppressed because one or more lines are too long

1
es/index.html Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
index.html Normal file

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-404-tsx","path":"/404.html","result":{"pageContext":{"language":"en","intl":{"language":"en","languages":["en","es"],"messages":{"71L0pp.defaultMessage":"Justice40","71L0pp.description":"Title of the project"},"routed":false,"originalPath":"/404/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-404-tsx","path":"/404/","result":{"pageContext":{"language":"en","intl":{"language":"en","languages":["en","es"],"messages":{"71L0pp.defaultMessage":"Justice40","71L0pp.description":"Title of the project"},"routed":false,"originalPath":"/404/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

1
page-data/app-data.json Normal file
View file

@ -0,0 +1 @@
{"webpackCompilationHash":"7c072212febea6e55278"}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-404-tsx","path":"/en/404.html","result":{"pageContext":{"language":"en","intl":{"language":"en","languages":["en","es"],"messages":{"71L0pp.defaultMessage":"Justice40","71L0pp.description":"Title of the project"},"routed":true,"originalPath":"/404.html","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-404-tsx","path":"/en/404/","matchPath":"/en/*","result":{"pageContext":{"matchPath":"/en/*","language":"en","intl":{"language":"en","languages":["en","es"],"messages":{"71L0pp.defaultMessage":"Justice40","71L0pp.description":"Title of the project"},"routed":true,"originalPath":"/404/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-index-tsx","path":"/en/","result":{"pageContext":{"language":"en","intl":{"language":"en","languages":["en","es"],"messages":{"71L0pp.defaultMessage":"Justice40","71L0pp.description":"Title of the project"},"routed":true,"originalPath":"/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-404-tsx","path":"/es/404.html","result":{"pageContext":{"language":"es","intl":{"language":"es","languages":["en","es"],"messages":{"71L0pp":"Justicia40"},"routed":true,"originalPath":"/404.html","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-404-tsx","path":"/es/404/","matchPath":"/es/*","result":{"pageContext":{"matchPath":"/es/*","language":"es","intl":{"language":"es","languages":["en","es"],"messages":{"71L0pp":"Justicia40"},"routed":true,"originalPath":"/404/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-index-tsx","path":"/es/","result":{"pageContext":{"language":"es","intl":{"language":"es","languages":["en","es"],"messages":{"71L0pp":"Justicia40"},"routed":true,"originalPath":"/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

View file

@ -0,0 +1 @@
{"componentChunkName":"component---src-pages-index-tsx","path":"/","result":{"pageContext":{"language":"en","intl":{"language":"en","languages":["en","es"],"messages":{"71L0pp.defaultMessage":"Justice40","71L0pp.description":"Title of the project"},"routed":false,"originalPath":"/","redirect":true,"defaultLanguage":"en"}}},"staticQueryHashes":[]}

File diff suppressed because one or more lines are too long

View file

@ -1,15 +0,0 @@
# Tile Server
## What is it?
A simple tile server using [pg_tileserv](https://github.com/CrunchyData/pg_tileserv), based on pg_tileserv [docker example](https://github.com/CrunchyData/pg_tileserv/tree/master/examples/docker).
## How to use it?
1. Edit variables in `docker-compose.yml` if necessary to customize username/pw
2. Run `docker-compose up` to start running the server. It will likely stall in enabling extensions (TODO: figure this out))
3. Restart the server with ctrl-c. It should load the data from the `data/` directory exactly one time.
## Using
- Point your visualization library to the following URL, and select `vector` tiles: `http://localhost:7800/public.maryland/{z}/{x}/{y}.mvt`

File diff suppressed because one or more lines are too long

View file

@ -1,36 +0,0 @@
version: "3.9"
services:
tileserv:
image: pramsey/pg_tileserv:20210210
environment:
- DATABASE_URL=postgresql://map_dev_user:map_pwd@db/map_dev
depends_on:
db:
condition: service_healthy
ports:
- 7800:7800
restart: unless-stopped
db:
image: kartoza/postgis:13-3.1
volumes:
- pgdata:/var/lib/postgresql/data
- ./data:/work
- ./load-data-db.sh:/docker-entrypoint-initdb.d/load_data-db.sh
environment:
- POSTGRES_USER=map_dev_user
- POSTGRES_PASS=map_pwd
- POSTGRES_DB=map_dev
- ALLOW_IP_RANGE=0.0.0.0/0
ports:
- 5434:5432
healthcheck:
test: ["CMD-SHELL", "pg_isready -h db -U map_dev_user -d map_dev"]
interval: 5s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
pgdata:

View file

@ -1,12 +0,0 @@
#!/usr/bin/env bash
set -euo pipefail
# echo "[SQL INIT SCRIPT] Creating extension..."
# psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "gis" <<-EOSQL
# CREATE EXTENSION IF NOT EXISTS postgis;
# EOSQL
# Load Maryland geojson
echo "[SQL INIT SCRIPT] Loading data from geojson..."
ogr2ogr -progress -f PostgreSQL PG:"host=localhost dbname=map_dev user=map_dev_user password=map_pwd" /work/maryland.geojson -nln maryland
echo "Data load complete"

Some files were not shown because too many files have changed in this diff Show more