v1.3 - added an sqlite db and built data structure
This commit is contained in:
parent
b0fba52c0e
commit
5efdafbb97
37 changed files with 6414 additions and 1988 deletions
174
src/data/mapGrid.js
Normal file
174
src/data/mapGrid.js
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
/* ═══════════════════════════════════════════════════════════════
|
||||
MAP GRID
|
||||
|
||||
Pure layout. Where each tile sits, what it's called, and how to
|
||||
work out which tile a chapter belongs to. None of this is in the
|
||||
database because none of it changes — Rhode Island will not be
|
||||
moving, and no admin form should offer to move it.
|
||||
|
||||
What IS in the database is which regions cover which areas, and
|
||||
how much of each. That arrives as region_areas rows whose
|
||||
area_code matches a key in AREAS below. A code with no match
|
||||
here simply doesn't paint, which is how Africa and the UK exist
|
||||
as regions with no tile.
|
||||
═══════════════════════════════════════════════════════════════ */
|
||||
|
||||
export const GRID_COLS = 13;
|
||||
export const GRID_ROWS = 7;
|
||||
|
||||
/* ── States ────────────────────────────────────────────────────
|
||||
[column, row], both 1-based. A tile grid rather than true
|
||||
geography: every state reads at the same size, it stays
|
||||
legible on a phone, and there's no map library to load.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
const STATE_GRID = {
|
||||
AK: [1, 1], ME: [13, 1],
|
||||
WA: [2, 2], ID: [3, 2], MT: [4, 2], ND: [5, 2], MN: [6, 2], WI: [7, 2],
|
||||
MI: [8, 3], NY: [10, 2], VT: [11, 2], NH: [12, 2],
|
||||
OR: [2, 3], NV: [3, 4], WY: [4, 3], SD: [5, 3], IA: [6, 3], IL: [7, 3],
|
||||
IN: [7, 4], OH: [8, 4], PA: [9, 2], NJ: [10, 3], MA: [11, 3],
|
||||
CA: [2, 4], UT: [3, 3], CO: [4, 4], NE: [5, 4], MO: [6, 4], KY: [7, 5],
|
||||
WV: [9, 3], VA: [9, 4], MD: [10, 5], DE: [10, 4], CT: [11, 4],
|
||||
AZ: [3, 5], NM: [4, 5], KS: [5, 5], AR: [6, 5], TN: [8, 5], NC: [10, 6],
|
||||
DC: [9, 5], RI: [12, 3],
|
||||
OK: [5, 6], LA: [6, 6], MS: [7, 6], AL: [8, 6], SC: [9, 6],
|
||||
HI: [1, 7], TX: [5, 7], GA: [9, 7], FL: [10, 7],
|
||||
};
|
||||
|
||||
/* ── Bands ─────────────────────────────────────────────────────
|
||||
Wide areas that aren't states. A band is just a tile with a
|
||||
span, which keeps the renderer from needing a second code path.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
const BAND_GRID = {
|
||||
CANADA: [3, 1, 8], // col, row, span
|
||||
};
|
||||
|
||||
export const AREA_NAMES = {
|
||||
AL: "Alabama", AK: "Alaska", AZ: "Arizona", AR: "Arkansas",
|
||||
CA: "California", CO: "Colorado", CT: "Connecticut", DE: "Delaware",
|
||||
DC: "District of Columbia", FL: "Florida", GA: "Georgia", HI: "Hawai'i",
|
||||
ID: "Idaho", IL: "Illinois", IN: "Indiana", IA: "Iowa", KS: "Kansas",
|
||||
KY: "Kentucky", LA: "Louisiana", ME: "Maine", MD: "Maryland",
|
||||
MA: "Massachusetts", MI: "Michigan", MN: "Minnesota", MS: "Mississippi",
|
||||
MO: "Missouri", MT: "Montana", NE: "Nebraska", NV: "Nevada",
|
||||
NH: "New Hampshire", NJ: "New Jersey", NM: "New Mexico", NY: "New York",
|
||||
NC: "North Carolina", ND: "North Dakota", OH: "Ohio", OK: "Oklahoma",
|
||||
OR: "Oregon", PA: "Pennsylvania", RI: "Rhode Island",
|
||||
SC: "South Carolina", SD: "South Dakota", TN: "Tennessee", TX: "Texas",
|
||||
UT: "Utah", VT: "Vermont", VA: "Virginia", WA: "Washington",
|
||||
WV: "West Virginia", WI: "Wisconsin", WY: "Wyoming",
|
||||
CANADA: "Canada",
|
||||
};
|
||||
|
||||
/* ── One list the renderer walks ───────────────────────────────
|
||||
States and bands unified, so drawing the map is a single map()
|
||||
over AREAS rather than two loops with different shapes.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
export const AREAS = Object.freeze([
|
||||
...Object.entries(STATE_GRID).map(([code, [col, row]]) => ({
|
||||
code,
|
||||
name: AREA_NAMES[code] ?? code,
|
||||
col,
|
||||
row,
|
||||
span: 1,
|
||||
isState: true,
|
||||
})),
|
||||
...Object.entries(BAND_GRID).map(([code, [col, row, span]]) => ({
|
||||
code,
|
||||
name: AREA_NAMES[code] ?? code,
|
||||
col,
|
||||
row,
|
||||
span,
|
||||
isState: false,
|
||||
})),
|
||||
]);
|
||||
|
||||
export const AREA_BY_CODE = Object.fromEntries(AREAS.map((a) => [a.code, a]));
|
||||
|
||||
/* ── Which tile a chapter sits on ──────────────────────────────
|
||||
The database stores a real address — state_code for US, and a
|
||||
country code otherwise — rather than a tile name. This is the
|
||||
one place that translates between the two, so adding a Mexico
|
||||
band later means a line here and nothing in SQL.
|
||||
|
||||
Returns null for anything with no tile, which covers virtual
|
||||
chapters and any country not drawn.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
const COUNTRY_AREA = {
|
||||
CA: "CANADA", // ISO country code, not California
|
||||
};
|
||||
|
||||
export function areaForChapter(chapter) {
|
||||
if (!chapter) return null;
|
||||
if (chapter.is_online) return null;
|
||||
|
||||
if (chapter.country === "US") {
|
||||
return AREA_BY_CODE[chapter.state_code] ? chapter.state_code : null;
|
||||
}
|
||||
return COUNTRY_AREA[chapter.country] ?? null;
|
||||
}
|
||||
|
||||
/* ── Slices per tile ───────────────────────────────────────────
|
||||
Turns region_areas rows into what the SVG needs: for each
|
||||
tile, the regions painting it and the fraction each takes.
|
||||
|
||||
A region with share 1 and no edge fills the tile. A shared
|
||||
tile has one row per region, each declaring its own slice, so
|
||||
California is two entries of 0.5 rather than a primary plus a
|
||||
remainder — no arithmetic, and the renderer doesn't need to
|
||||
know which region "really" owns it.
|
||||
|
||||
regionAreas [{ region_id, area_code, share, edge, note }]
|
||||
regions [{ id, name, color, ... }]
|
||||
───────────────────────────────────────────────────────────── */
|
||||
export function buildAreaSlices(regionAreas = [], regions = []) {
|
||||
const regionById = Object.fromEntries(regions.map((r) => [r.id, r]));
|
||||
const byArea = {};
|
||||
|
||||
for (const row of regionAreas) {
|
||||
const area = AREA_BY_CODE[row.area_code];
|
||||
const region = regionById[row.region_id];
|
||||
if (!area || !region) continue; // untiled region, or unknown code
|
||||
|
||||
(byArea[row.area_code] ??= []).push({
|
||||
regionId: region.id,
|
||||
name: region.name,
|
||||
color: region.color,
|
||||
share: row.share ?? 1,
|
||||
edge: row.edge ?? null,
|
||||
note: row.note ?? null,
|
||||
});
|
||||
}
|
||||
|
||||
// Full-tile slice first, so a partial slice paints over it.
|
||||
for (const slices of Object.values(byArea)) {
|
||||
slices.sort((a, b) => b.share - a.share);
|
||||
}
|
||||
|
||||
return byArea;
|
||||
}
|
||||
|
||||
/* ── Chapter counts per tile ───────────────────────────────────
|
||||
{ WA: 2, MO: 1, CANADA: 1 }
|
||||
───────────────────────────────────────────────────────────── */
|
||||
export function countChaptersByArea(chapters = []) {
|
||||
const counts = {};
|
||||
for (const chapter of chapters) {
|
||||
const code = areaForChapter(chapter);
|
||||
if (code) counts[code] = (counts[code] ?? 0) + 1;
|
||||
}
|
||||
return counts;
|
||||
}
|
||||
|
||||
/* ── "AK, ID, MT, OR, UT (Salt Lake City area), WA" ────────────
|
||||
Replaces statesLabel. Note the annotation now comes straight
|
||||
from the row rather than being looked up in a splits table and
|
||||
branched on whether this region is the primary.
|
||||
───────────────────────────────────────────────────────────── */
|
||||
export function areasLabel(regionId, regionAreas = []) {
|
||||
return regionAreas
|
||||
.filter((row) => row.region_id === regionId && row.area_code !== "CANADA")
|
||||
.map((row) => (row.note ? `${row.area_code} (${row.note})` : row.area_code))
|
||||
.sort()
|
||||
.join(", ");
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue