Skip to content

Commit

Permalink
Merge branch 'main' into mvt-pmtiles-api
Browse files Browse the repository at this point in the history
  • Loading branch information
nofurtherinformation committed Sep 25, 2024
2 parents d5646b9 + f9530ce commit cfa9298
Show file tree
Hide file tree
Showing 12 changed files with 279 additions and 79 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ scratch/
PG:*

*.dev
sample_data/*
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,28 @@ The Districtr reboot monorepo.
- [`pipelines`](pipelines/): Data pipelines, ETL. Not a main focus of the reboot. For now, will mostly contain scratch data transformation scripts before being integrated into the backend CLI.
- [`prototypes`](prototypes/): Prototypes conducted as part of the reboot.

## Quickstart

The backend (Python), frontend (NextJS), and database (postgres) can be run locally using Docker.

- Install and configure [Docker](https://www.docker.com/) for your machine
- From the repo root, run `docker-compose up --build`
- To load in data, add data to a folder `sample_data` in the repo root, and in `docker-compose.yml` set `services > backend > environment > LOAD_GERRY_DB_DATA` to `true`. You can change where the script looks for available data with the `GPKG_DATA_DIR` variable.

## Districtr reboot architecture

After experimenting with various technologies (see [`prototypes`](prototypes/)) we landed on the following architecture for the Districtr reboot:

![Districtr architecture](docs/images/districtr-architecture.png "Districtr architecture")

The redesign aims to principally to address three key pain points in the Districtr application’s performance and maintainability:

1. Slow tile rendering
1. Cumbersome use of tiles as global state for tile rendering and most metric calculation
1. Complexity and poor interoperability in architecture without slow copies

And two key feature additions

1. Block “shattering”
1. A headless CMS (this will be added in a later phase of work / is not currently a focus of the reboot)

Expand Down
2 changes: 2 additions & 0 deletions app/.env.docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
NEXT_PUBLIC_API_URL=http://localhost:8000
NEXT_PUBLIC_S3_BUCKET_URL=https://pub-fa71193941a74e14a38eee99f30f53d9.r2.dev
20 changes: 20 additions & 0 deletions app/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use official Node.js image as the base image
FROM node:18-alpine

# Set working directory inside the container
WORKDIR /app

# Copy package.json and package-lock.json to install dependencies first
COPY package.json package-lock.json ./

# Install dependencies
RUN npm install

# Copy the rest of the application code
COPY . .

# Expose the Next.js dev server port
EXPOSE 3000

# Start the Next.js application in development mode
CMD ["npm", "run", "dev"]
4 changes: 2 additions & 2 deletions app/src/app/components/sidebar/ColorPicker.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, { useState } from "react";
import { palette, color10 } from "../../constants/colors";
import { _colorScheme } from "../../constants/colors";
import { Button } from "@radix-ui/themes";
import { styled } from "@stitches/react";
import * as RadioGroup from "@radix-ui/react-radio-group";
Expand All @@ -22,7 +22,7 @@ export function ColorPicker() {
accumulatedGeoids: state.accumulatedGeoids,
resetAccumulatedBlockPopulations: state.resetAccumulatedBlockPopulations,
}));
const colorArray = color10;
const colorArray = _colorScheme;
if (!colorArray) return null;
const handleRadioChange = (value) => {
console.log(
Expand Down
10 changes: 7 additions & 3 deletions app/src/app/components/sidebar/charts/HorizontalBarChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
YAxis,
Cell,
} from "recharts";
import { color10 } from "@/app/constants/colors";
import { colorScheme } from "@/app/constants/colors";

type TooltipInput = {
active?: boolean;
Expand Down Expand Up @@ -58,7 +58,8 @@ export const HorizontalBar = () => {
</Heading>
<ResponsiveContainer
width="100%"
height={color10.length * 18}
// should this instead be set based on the target number of zones? see https://github.com/districtr/districtr-v2/issues/92
height={colorScheme.length * 18}
minHeight="200px"
>
<BarChart
Expand All @@ -80,7 +81,10 @@ export const HorizontalBar = () => {
{mapMetrics.data
.sort((a, b) => a.zone - b.zone)
.map((entry, index) => (
<Cell key={`cell-${index}`} fill={color10[entry.zone - 1]} />
<Cell
key={`cell-${index}`}
fill={colorScheme[entry.zone - 1]}
/>
))}
</Bar>
</BarChart>
Expand Down
102 changes: 43 additions & 59 deletions app/src/app/constants/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,63 +27,47 @@ import {
sky,
} from "@radix-ui/colors";

export const palette = {
colors: {
...(tomato as object),
...(red as object),
...(ruby as object),
...(crimson as object),
...(pink as object),
...(plum as object),
...(purple as object),
...(violet as object),
...(iris as object),
...(indigo as object),
...(blue as object),
...(cyan as object),
...(teal as object),
...(jade as object),
...(green as object),
...(grass as object),
...(orange as object),
...(amber as object),
...(yellow as object),
...(gold as object),
...(brown as object),
...(bronze as object),
...(gray as object),
...(mint as object),
...(lime as object),
...(sky as object),
} as { [key: string]: { [key: string]: string } },
};

// bright colors!
export const color10 = [
tomato.tomato10,
red.red10,
ruby.ruby10,
crimson.crimson10,
pink.pink10,
plum.plum10,
purple.purple10,
violet.violet10,
iris.iris10,
indigo.indigo10,
blue.blue10,
cyan.cyan10,
teal.teal10,
jade.jade10,
green.green10,
grass.grass10,
orange.orange10,
amber.amber10,
yellow.yellow10,
gold.gold10,
brown.brown10,
bronze.bronze10,
gray.gray10,
mint.mint10,
lime.lime10,
sky.sky10,
export const colorScheme = [
"#0099cd",
"#ffca5d",
"#00cd99",
"#99cd00",
"#cd0099",
"#aa44ef", // lighter, req from San Diego
// Color brewer:
"#8dd3c7",
"#bebada",
"#fb8072",
"#80b1d3",
"#fdb462",
"#b3de69",
"#fccde5",
"#bc80bd",
"#ccebc5",
"#ffed6f",
"#ffffb3",
// other color brewer scheme:
"#a6cee3",
"#1f78b4",
"#b2df8a",
"#33a02c",
"#fb9a99",
"#e31a1c",
"#fdbf6f",
"#ff7f00",
"#cab2d6",
"#6a3d9a",
"#b15928",
// random material design colors:
"#64ffda",
"#00B8D4",
"#A1887F",
"#76FF03",
"#DCE775",
"#B388FF",
"#FF80AB",
"#D81B60",
"#26A69A",
"#FFEA00",
"#6200EA",
];
34 changes: 19 additions & 15 deletions app/src/app/constants/layers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { MutableRefObject } from "react";
import { Map } from "maplibre-gl";
import { getBlocksSource } from "./sources";
import { gerryDBView } from "../api/apiHandlers";
import { color10 } from "./colors";
import { colorScheme } from "./colors";

export const BLOCK_SOURCE_ID = "blocks";
export const BLOCK_LAYER_ID = "blocks";
Expand All @@ -23,10 +23,13 @@ export const COUNTY_LAYER_IDS: string[] = [
export const LABELS_BREAK_LAYER_ID = "places_subplace";

const colorStyleBaseline: any[] = ["case"];
export const ZONE_ASSIGNMENT_STYLE_DYNAMIC = color10.reduce((val, color, i) => {
val.push(["==", ["feature-state", "zone"], i + 1], color); // 1-indexed per mapStore.ts
return val;
}, colorStyleBaseline);
export const ZONE_ASSIGNMENT_STYLE_DYNAMIC = _colorScheme.reduce(
(val, color, i) => {
val.push(["==", ["feature-state", "zone"], i + 1], color); // 1-indexed per mapStore.ts
return val;
},
colorStyleBaseline
);
ZONE_ASSIGNMENT_STYLE_DYNAMIC.push("#cecece");

// cast the above as an ExpressionSpecification
Expand All @@ -35,7 +38,7 @@ export const ZONE_ASSIGNMENT_STYLE: ExpressionSpecification =
ZONE_ASSIGNMENT_STYLE_DYNAMIC;

export function getBlocksLayerSpecification(
sourceLayer: string,
sourceLayer: string
): LayerSpecification {
return {
id: BLOCK_LAYER_ID,
Expand All @@ -58,7 +61,7 @@ export function getBlocksLayerSpecification(
}

export function getBlocksHoverLayerSpecification(
sourceLayer: string,
sourceLayer: string
): LayerSpecification {
return {
id: BLOCK_HOVER_LAYER_ID,
Expand All @@ -70,7 +73,7 @@ export function getBlocksHoverLayerSpecification(
},
paint: {
"fill-opacity": [
"case",
"case",
// zone is selected and hover is true and hover is not null
[
"all",
Expand All @@ -81,7 +84,7 @@ export function getBlocksHoverLayerSpecification(
// @ts-ignore
["!", ["==", ["feature-state", "hover"], null]], //< desired behavior but typerror
["boolean", ["feature-state", "hover"], true],
]
],
],
0.9,
// zone is selected and hover is false, and hover is not null
Expand All @@ -94,16 +97,17 @@ export function getBlocksHoverLayerSpecification(
// @ts-ignore
["!", ["==", ["feature-state", "hover"], null]], //< desired behavior but typerror
["boolean", ["feature-state", "hover"], false],
]
],
],
0.7,
// zone is selected, fallback, regardless of hover state
// @ts-ignore
["!", ["==", ["feature-state", "zone"], null]], //< desired behavior but typerror
0.7,
// hover is true, fallback, regardless of zone state
["boolean", ["feature-state", "hover"], false], 0.6,
0.2
["boolean", ["feature-state", "hover"], false],
0.6,
0.2,
],
"fill-color": ZONE_ASSIGNMENT_STYLE || "#000000",
},
Expand All @@ -112,18 +116,18 @@ export function getBlocksHoverLayerSpecification(

const addBlockLayers = (
map: MutableRefObject<Map | null>,
gerryDBView: gerryDBView,
gerryDBView: gerryDBView
) => {
const blockSource = getBlocksSource(gerryDBView.tiles_s3_path);
removeBlockLayers(map);
map.current?.addSource(BLOCK_SOURCE_ID, blockSource);
map.current?.addLayer(
getBlocksLayerSpecification(gerryDBView.name),
LABELS_BREAK_LAYER_ID,
LABELS_BREAK_LAYER_ID
);
map.current?.addLayer(
getBlocksHoverLayerSpecification(gerryDBView.name),
LABELS_BREAK_LAYER_ID,
LABELS_BREAK_LAYER_ID
);
};

Expand Down
19 changes: 19 additions & 0 deletions backend/.env.docker
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Backend
DOMAIN=localhost
ENVIRONMENT=local
PROJECT_NAME="Districtr v2 backend"
BACKEND_CORS_ORIGINS="http://localhost,http://localhost:5173,http://localhost:3000,http://127.0.0.1:3000"
SECRET_KEY="super-secret"

# Postgres
DATABASE_URL=postgresql+psycopg://postgres:postgres@db:5432/districtr
POSTGRES_SCHEME=postgresql+psycopg
POSTGRES_USER=postgres
POSTGRES_PASSWORD=postgres
POSTGRES_DB=districtr
POSTGRES_SERVER=db # Use the service name `db` from docker-compose
POSTGRES_PORT=5432

# Volumes
VOLUME_PATH=/data

20 changes: 20 additions & 0 deletions backend/Dockerfile.dev
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Use official Python image with version >= 3.11
FROM python:3.12-slim

# Set working directory inside the container
WORKDIR /districtr-backend

# Copy requirements file first and install dependencies
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir -r requirements.txt

# Copy the rest of the backend code into the container
COPY . .

# Install PostGIS extension and PostgreSQL client for database operations
RUN apt-get update && apt-get install -y postgresql-client libpq-dev gdal-bin

# Command to run the server
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--reload"]
Loading

0 comments on commit cfa9298

Please sign in to comment.