Initial commit
This commit is contained in:
commit
af60cf93f7
2
data/.gitignore
vendored
Normal file
2
data/.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
*
|
||||
!.gitignore
|
45
get_data.py
Normal file
45
get_data.py
Normal file
|
@ -0,0 +1,45 @@
|
|||
import requests
|
||||
import csv
|
||||
import json
|
||||
|
||||
# https://www2.census.gov/programs-surveys/popest/technical-documentation/file-layouts/2020-2022/SUB-EST2022.pdf
|
||||
INCORPORATED_PLACE = "162"
|
||||
|
||||
res = requests.get("https://www2.census.gov/programs-surveys/popest/datasets/2020-2022/cities/totals/sub-est2022.csv")
|
||||
res.raise_for_status()
|
||||
|
||||
cities_by_state = {}
|
||||
for line in csv.DictReader(res.content.decode('utf-8-sig').split('\n')):
|
||||
if line['SUMLEV'] != INCORPORATED_PLACE:
|
||||
continue
|
||||
|
||||
if not line['STNAME'] in cities_by_state:
|
||||
cities_by_state[line['STNAME']] = []
|
||||
|
||||
cities_by_state[line['STNAME']].append({
|
||||
"name": " ".join(line['NAME'].split(" ")[:-1]), # Remove "city" or "town" from the end
|
||||
"pop": int(line['POPESTIMATE2022']),
|
||||
})
|
||||
|
||||
for state, cities in cities_by_state.items():
|
||||
cities.sort(key=lambda i: i["pop"], reverse=True)
|
||||
|
||||
with open(f"data/{state}.json", 'w') as f:
|
||||
f.write(json.dumps(cities))
|
||||
|
||||
with open(f"data/states.json", 'w') as f:
|
||||
f.write(json.dumps(list(cities_by_state.keys())))
|
||||
|
||||
# ----- MAP -----
|
||||
|
||||
import subprocess
|
||||
|
||||
CMD="""
|
||||
curl --silent --remote-name https://www2.census.gov/geo/tiger/GENZ2022/shp/cb_2022_us_state_20m.zip
|
||||
unzip -q -o cb_2022_us_state_20m.zip
|
||||
ogr2ogr -f GeoJSON data/states.geojson cb_2022_us_state_20m.shp
|
||||
sed -i '/^"crs":/d' data/states.geojson
|
||||
rm cb_2022_us_state_20m.*
|
||||
"""
|
||||
|
||||
subprocess.run(CMD, shell=True)
|
126
index.html
Normal file
126
index.html
Normal file
|
@ -0,0 +1,126 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-100">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Name All the Cities</title>
|
||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
|
||||
</head>
|
||||
<body class="h-100 d-flex flex-column">
|
||||
<div class="container">
|
||||
<h1>Name All the Cities</h1>
|
||||
<div class="mb-3">
|
||||
<label for="state-select" class="form-label">State:</label>
|
||||
<div class="input-group">
|
||||
<select id="state-select" class="form-select" disabled>
|
||||
<option select>Choose a state…</option>
|
||||
</select>
|
||||
<button class="btn btn-primary" type="button" id="go-button">Play</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script type="module">
|
||||
(async function init(){
|
||||
const response = await fetch("data/states.json");
|
||||
const states = await response.json();
|
||||
|
||||
const ss = document.getElementById('state-select');
|
||||
ss.disabled = false;
|
||||
for (let state of states) {
|
||||
ss.options[ss.options.length] = new Option(state);
|
||||
}
|
||||
|
||||
document.getElementById('go-button').addEventListener('click', async (e) => {
|
||||
e.target.disabled = true;
|
||||
ss.disabled = true;
|
||||
await selectState(ss.value);
|
||||
document.getElementById('game').style.display = 'block';
|
||||
});
|
||||
})();
|
||||
|
||||
import { createApp } from 'https://unpkg.com/petite-vue?module'
|
||||
|
||||
// https://stackoverflow.com/a/13627586
|
||||
function ordinal_suffix_of(i) {
|
||||
let j = i % 10,
|
||||
k = i % 100;
|
||||
if (j === 1 && k !== 11) {
|
||||
return i + "st";
|
||||
}
|
||||
if (j === 2 && k !== 12) {
|
||||
return i + "nd";
|
||||
}
|
||||
if (j === 3 && k !== 13) {
|
||||
return i + "rd";
|
||||
}
|
||||
return i + "th";
|
||||
}
|
||||
|
||||
const simplify = s => s.toLowerCase().replace(/[^a-z]/g, '');
|
||||
|
||||
async function selectState(stateName) {
|
||||
const response = await fetch(`data/${stateName}.json`);
|
||||
const cities = await response.json();
|
||||
console.log(cities)
|
||||
|
||||
window.app = createApp({
|
||||
state: stateName,
|
||||
cities: cities,
|
||||
simplified_cities: cities.map(city => simplify(city.name)),
|
||||
city_guess: "",
|
||||
message: "",
|
||||
guess() {
|
||||
const rank = this.simplified_cities.indexOf(simplify(this.city_guess))
|
||||
if (rank >= 0) {
|
||||
const city = this.cities[rank];
|
||||
|
||||
if (!city.guessed) {
|
||||
city.guessed = true;
|
||||
this.message = `${city.name} (population ${city.pop}) is the ${ordinal_suffix_of(rank)} most populated city in ${this.state}.`;
|
||||
this.city_guess = "";
|
||||
} else {
|
||||
this.message = `Already guessed ${city.name} (population ${city.pop}, rank ${rank}).`;
|
||||
this.city_guess = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}).mount()
|
||||
}
|
||||
</script>
|
||||
<div id="game" v-scope style="display: none;">
|
||||
<div class="mb-3">
|
||||
<label for="city-guess" class="form-label">City:</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" id="city-guess" v-model="city_guess" @keyup.enter="guess">
|
||||
<button class="btn btn-primary" type="button" @click="guess">Guess</button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="message != ''" class="alert alert-secondary" role="alert">
|
||||
{{ message }}
|
||||
</div>
|
||||
<h3>{{ state }} cities ({{ cities.filter(c => c.guessed).length }}/{{ cities.length }})</h3>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Rank</th>
|
||||
<th>Name</th>
|
||||
<th>Population</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<template v-for="(city, rank) in cities">
|
||||
<tr v-show="city.guessed">
|
||||
<td>{{ rank + 1 }}</td>
|
||||
<td>{{ city.name }}</td>
|
||||
<td>{{ city.pop }}</td>
|
||||
</tr>
|
||||
</template>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer mt-auto py-3 text-center text-muted">
|
||||
Created by <a href="https://chandlerswift.com">Chandler Swift</a> using 2020 US Census data
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue