Add warning map border overlays and feathered fills
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 50 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 96 KiB |
@@ -1,4 +1,4 @@
|
|||||||
import { createEffect, createMemo, createResource, createSignal, For, onMount, Show } from 'solid-js';
|
import { createEffect, createMemo, createResource, createSignal, For, Show } from 'solid-js';
|
||||||
import { apiHost } from '../../consts';
|
import { apiHost } from '../../consts';
|
||||||
import { download } from '../../helpers/download';
|
import { download } from '../../helpers/download';
|
||||||
import { LoadingSpinner } from '../../components/spinner/LoadingSpinner';
|
import { LoadingSpinner } from '../../components/spinner/LoadingSpinner';
|
||||||
@@ -19,7 +19,7 @@ export function Warnings() {
|
|||||||
const [title, setTitle] = createSignal('');
|
const [title, setTitle] = createSignal('');
|
||||||
const [selectedDate, setSelectedDate] = createSignal('all');
|
const [selectedDate, setSelectedDate] = createSignal('all');
|
||||||
const [phenomenon, setPhenomenon] = createSignal('all');
|
const [phenomenon, setPhenomenon] = createSignal('all');
|
||||||
const [background, setBackground] = createSignal<HTMLImageElement>();
|
const [artwork, setArtwork] = createSignal<{ background: HTMLImageElement; border: HTMLImageElement }>();
|
||||||
const [data, { refetch }] = createResource(fetchWarnings);
|
const [data, { refetch }] = createResource(fetchWarnings);
|
||||||
let canvas!: HTMLCanvasElement;
|
let canvas!: HTMLCanvasElement;
|
||||||
|
|
||||||
@@ -33,24 +33,33 @@ export function Warnings() {
|
|||||||
return dateMatches && phenomenonMatches;
|
return dateMatches && phenomenonMatches;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const loadBackground = () => {
|
const loadArtwork = () => {
|
||||||
const image = new Image();
|
const current = template();
|
||||||
image.onload = () => setBackground(image);
|
setArtwork(undefined);
|
||||||
image.src = template().map;
|
const background = new Image();
|
||||||
|
const border = new Image();
|
||||||
|
let loaded = 0;
|
||||||
|
const finish = () => {
|
||||||
|
loaded += 1;
|
||||||
|
if (loaded === 2 && template() === current) setArtwork({ background, border });
|
||||||
|
};
|
||||||
|
background.onload = finish;
|
||||||
|
border.onload = finish;
|
||||||
|
background.src = current.map;
|
||||||
|
border.src = current.border;
|
||||||
};
|
};
|
||||||
|
|
||||||
onMount(loadBackground);
|
createEffect(() => { output(); loadArtwork(); });
|
||||||
createEffect(() => { output(); loadBackground(); });
|
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
const image = background();
|
const images = artwork();
|
||||||
const current = template();
|
const current = template();
|
||||||
const warnings = filteredWarnings();
|
const warnings = filteredWarnings();
|
||||||
const currentTitle = title();
|
const currentTitle = title();
|
||||||
if (!canvas || !image) return;
|
if (!canvas || !images) return;
|
||||||
if (canvas.width !== current.width) canvas.width = current.width;
|
if (canvas.width !== current.width) canvas.width = current.width;
|
||||||
if (canvas.height !== current.height) canvas.height = current.height;
|
if (canvas.height !== current.height) canvas.height = current.height;
|
||||||
void document.fonts.load(`700 ${current.title.fontSize}px Monda`).then(() =>
|
void document.fonts.load(`700 ${current.title.fontSize}px Monda`).then(() =>
|
||||||
drawWarningMap(canvas.getContext('2d')!, image, current, warnings, currentTitle),
|
drawWarningMap(canvas.getContext('2d')!, images.background, images.border, current, warnings, currentTitle),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ export function warningSeverity(value: string): Severity {
|
|||||||
export function drawWarningMap(
|
export function drawWarningMap(
|
||||||
ctx: CanvasRenderingContext2D,
|
ctx: CanvasRenderingContext2D,
|
||||||
background: HTMLImageElement,
|
background: HTMLImageElement,
|
||||||
|
border: HTMLImageElement,
|
||||||
template: WarningTemplate,
|
template: WarningTemplate,
|
||||||
warnings: WeatherWarning[],
|
warnings: WeatherWarning[],
|
||||||
title: string,
|
title: string,
|
||||||
@@ -40,9 +41,23 @@ export function drawWarningMap(
|
|||||||
ctx.clearRect(0, 0, template.width, template.height);
|
ctx.clearRect(0, 0, template.width, template.height);
|
||||||
ctx.drawImage(background, 0, 0, template.width, template.height);
|
ctx.drawImage(background, 0, 0, template.width, template.height);
|
||||||
|
|
||||||
|
const warningLayer = document.createElement('canvas');
|
||||||
|
warningLayer.width = template.width;
|
||||||
|
warningLayer.height = template.height;
|
||||||
|
const warningContext = warningLayer.getContext('2d')!;
|
||||||
|
|
||||||
[...warnings]
|
[...warnings]
|
||||||
.sort((a, b) => severityRank(warningSeverity(a.intensity)) - severityRank(warningSeverity(b.intensity)))
|
.sort((a, b) => severityRank(warningSeverity(a.intensity)) - severityRank(warningSeverity(b.intensity)))
|
||||||
.forEach(warning => drawWarning(ctx, template, warning));
|
.forEach(warning => drawWarning(warningContext, template, warning));
|
||||||
|
|
||||||
|
ctx.save();
|
||||||
|
ctx.filter = `blur(${template.feather}px)`;
|
||||||
|
ctx.drawImage(warningLayer, 0, 0);
|
||||||
|
ctx.restore();
|
||||||
|
|
||||||
|
// The newsroom-authored outline is deliberately last so it hides small
|
||||||
|
// projection differences and remains crisp above the softened data layer.
|
||||||
|
ctx.drawImage(border, 0, 0, template.width, template.height);
|
||||||
|
|
||||||
drawTitle(ctx, template, title);
|
drawTitle(ctx, template, title);
|
||||||
drawLegend(ctx, template, warnings);
|
drawLegend(ctx, template, warnings);
|
||||||
@@ -62,10 +77,6 @@ function drawWarning(ctx: CanvasRenderingContext2D, template: WarningTemplate, w
|
|||||||
ctx.closePath();
|
ctx.closePath();
|
||||||
ctx.fillStyle = style.fill;
|
ctx.fillStyle = style.fill;
|
||||||
ctx.fill();
|
ctx.fill();
|
||||||
ctx.strokeStyle = style.stroke;
|
|
||||||
ctx.lineWidth = template.width === 1920 ? 3 : 5;
|
|
||||||
ctx.lineJoin = 'round';
|
|
||||||
ctx.stroke();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +107,15 @@ function drawTitle(ctx: CanvasRenderingContext2D, template: WarningTemplate, val
|
|||||||
ctx.textAlign = 'left';
|
ctx.textAlign = 'left';
|
||||||
ctx.textBaseline = 'middle';
|
ctx.textBaseline = 'middle';
|
||||||
ctx.fillText(text, x + layout.horizontalPadding, layout.top + layout.height / 2);
|
ctx.fillText(text, x + layout.horizontalPadding, layout.top + layout.height / 2);
|
||||||
|
|
||||||
|
const sourceHeight = layout.height * .52;
|
||||||
|
const sourceWidth = Math.max(layout.height * 2.45, width * .38);
|
||||||
|
ctx.fillStyle = 'rgba(245, 245, 245, .94)';
|
||||||
|
ctx.fillRect(x + width - sourceWidth, layout.top + layout.height, sourceWidth, sourceHeight);
|
||||||
|
ctx.fillStyle = '#111';
|
||||||
|
ctx.font = `700 ${fontSize * .55}px Monda`;
|
||||||
|
ctx.textAlign = 'center';
|
||||||
|
ctx.fillText('LVĢMC', x + width - sourceWidth / 2, layout.top + layout.height + sourceHeight / 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawLegend(ctx: CanvasRenderingContext2D, template: WarningTemplate, warnings: WeatherWarning[]) {
|
function drawLegend(ctx: CanvasRenderingContext2D, template: WarningTemplate, warnings: WeatherWarning[]) {
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import map1920 from '../../assets/map_1920x1080.webp';
|
import map1920 from '../../assets/map_1920x1080.webp';
|
||||||
import map3840 from '../../assets/map_3840x1440.webp';
|
import map3840 from '../../assets/map_3840x1440.webp';
|
||||||
|
import border1920 from '../../assets/production/BRIDINAJUMI_BORDER_1920.png';
|
||||||
|
import border3840 from '../../assets/production/BRIDINAJUMI_BORDER_3840.png';
|
||||||
|
|
||||||
export type WarningOutput = '1920x1080' | '3840x1440';
|
export type WarningOutput = '1920x1080' | '3840x1440';
|
||||||
|
|
||||||
@@ -7,6 +9,8 @@ export type WarningTemplate = {
|
|||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
map: string;
|
map: string;
|
||||||
|
border: string;
|
||||||
|
feather: number;
|
||||||
geo: { west: number; east: number; north: number; south: number; left: number; right: number; top: number; bottom: number };
|
geo: { west: number; east: number; north: number; south: number; left: number; right: number; top: number; bottom: number };
|
||||||
title: { right: number; top: number; height: number; fontSize: number; horizontalPadding: number; maximumWidth: number };
|
title: { right: number; top: number; height: number; fontSize: number; horizontalPadding: number; maximumWidth: number };
|
||||||
legend: { right: number; bottom: number; swatch: number; gap: number; fontSize: number };
|
legend: { right: number; bottom: number; swatch: number; gap: number; fontSize: number };
|
||||||
@@ -19,6 +23,8 @@ export const warningTemplates: Record<WarningOutput, WarningTemplate> = {
|
|||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
map: map1920,
|
map: map1920,
|
||||||
|
border: border1920,
|
||||||
|
feather: 16,
|
||||||
geo: { west: 20.97, east: 28.24, north: 58.09, south: 55.67, left: 360, right: 1655, top: 115, bottom: 864 },
|
geo: { west: 20.97, east: 28.24, north: 58.09, south: 55.67, left: 360, right: 1655, top: 115, bottom: 864 },
|
||||||
title: { right: 88, top: 38, height: 62, fontSize: 30, horizontalPadding: 24, maximumWidth: 900 },
|
title: { right: 88, top: 38, height: 62, fontSize: 30, horizontalPadding: 24, maximumWidth: 900 },
|
||||||
legend: { right: 88, bottom: 58, swatch: 27, gap: 14, fontSize: 20 },
|
legend: { right: 88, bottom: 58, swatch: 27, gap: 14, fontSize: 20 },
|
||||||
@@ -27,6 +33,8 @@ export const warningTemplates: Record<WarningOutput, WarningTemplate> = {
|
|||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1440,
|
height: 1440,
|
||||||
map: map3840,
|
map: map3840,
|
||||||
|
border: border3840,
|
||||||
|
feather: 28,
|
||||||
geo: { west: 20.97, east: 28.24, north: 58.09, south: 55.67, left: 985, right: 3150, top: 80, bottom: 1400 },
|
geo: { west: 20.97, east: 28.24, north: 58.09, south: 55.67, left: 985, right: 3150, top: 80, bottom: 1400 },
|
||||||
title: { right: 376, top: 76, height: 124, fontSize: 60, horizontalPadding: 48, maximumWidth: 1700 },
|
title: { right: 376, top: 76, height: 124, fontSize: 60, horizontalPadding: 48, maximumWidth: 1700 },
|
||||||
legend: { right: 376, bottom: 76, swatch: 42, gap: 22, fontSize: 30 },
|
legend: { right: 376, bottom: 76, swatch: 42, gap: 22, fontSize: 30 },
|
||||||
|
|||||||
Reference in New Issue
Block a user