Anchor Faktiska symbols to temperature badges
This commit is contained in:
@@ -67,6 +67,8 @@ Fixed positions:
|
|||||||
|
|
||||||
The weather symbols are not automatically derived from the historical temperature query. They remain editorial/manual inputs until a reliable phenomena-data mapping is designed and validated.
|
The weather symbols are not automatically derived from the historical temperature query. They remain editorial/manual inputs until a reliable phenomena-data mapping is designed and validated.
|
||||||
|
|
||||||
|
Symbol selection is manual, but placement is automatic. Each Faktiskā position owns a fixed marker layout that attaches the selected font glyph to the actual rendered edge of its temperature badge. Operators do not position symbols for each export; per-station offsets can be refined centrally against the production master.
|
||||||
|
|
||||||
Valmiera is included in the development seed so all 13 fixed positions can be exercised. Monta is the production typeface; Rubik remains temporary until the authorized Monta font files are supplied. The export dimensions are locked, but final pixel-level placement must still be compared against the production masters after Monta is installed.
|
Valmiera is included in the development seed so all 13 fixed positions can be exercised. Monta is the production typeface; Rubik remains temporary until the authorized Monta font files are supplied. The export dimensions are locked, but final pixel-level placement must still be compared against the production masters after Monta is installed.
|
||||||
|
|
||||||
## Latvia overview
|
## Latvia overview
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
|
|||||||
getWindData(),
|
getWindData(),
|
||||||
[getTitle(), getSource()],
|
[getTitle(), getSource()],
|
||||||
props,
|
props,
|
||||||
|
productionTemplate,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
img.src = props.map;
|
img.src = props.map;
|
||||||
@@ -76,6 +77,7 @@ export const MapView: Component<{ type: MapResolution, data: () => ResultKeyVal[
|
|||||||
getWindData(),
|
getWindData(),
|
||||||
[getTitle(), getSource()],
|
[getTitle(), getSource()],
|
||||||
props,
|
props,
|
||||||
|
productionTemplate,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { ResolutionPropsValue, WindProps, windProps } from "./mapConsts";
|
import { ResolutionPropsValue, WindProps, windProps } from "./mapConsts";
|
||||||
import { drawRotatedImage, getAngleFromString } from "./windAngles";
|
import { drawRotatedImage, getAngleFromString } from "./windAngles";
|
||||||
|
import { faktiskaMarkerLayout, FaktiskaStation } from "../../pages/map-graphics/faktiskaConfig";
|
||||||
|
|
||||||
// cityName, {x, y}, weatherValue, icon
|
// cityName, {x, y}, weatherValue, icon
|
||||||
export type CityData = [cityName: string, {x: number, y: number }, weatherValue: number, icon: string | undefined]
|
export type CityData = [cityName: string, {x: number, y: number }, weatherValue: number, icon: string | undefined]
|
||||||
@@ -11,6 +12,7 @@ export function drawOnMap(
|
|||||||
windData: [string, string, string, boolean, WindProps],
|
windData: [string, string, string, boolean, WindProps],
|
||||||
overlayData: [string, string],
|
overlayData: [string, string],
|
||||||
props: ResolutionPropsValue,
|
props: ResolutionPropsValue,
|
||||||
|
productionTemplate = false,
|
||||||
): void {
|
): void {
|
||||||
const [windDirection, windSpeed, windGusts, roundValues, windProp] = windData;
|
const [windDirection, windSpeed, windGusts, roundValues, windProp] = windData;
|
||||||
const [bgImg, arrowImg] = imgArr;
|
const [bgImg, arrowImg] = imgArr;
|
||||||
@@ -27,13 +29,13 @@ export function drawOnMap(
|
|||||||
const stringValue = numericValue.toString().replace(".", ",");
|
const stringValue = numericValue.toString().replace(".", ",");
|
||||||
|
|
||||||
ctx.font = `bold ${props.fontSize}px Rubik`;
|
ctx.font = `bold ${props.fontSize}px Rubik`;
|
||||||
const badgeWidth = Math.max(props.boxSize, Math.ceil(ctx.measureText(stringValue).width + props.fontSize * 0.8));
|
const badgeWidth = getBadgeWidth(ctx, stringValue, props);
|
||||||
ctx.fillStyle = "#FFFFFF";
|
ctx.fillStyle = "#FFFFFF";
|
||||||
ctx.fillRect(localX - badgeWidth / 2, localY - props.boxSize / 2, badgeWidth, props.boxSize);
|
ctx.fillRect(localX - badgeWidth / 2, localY - props.boxSize / 2, badgeWidth, props.boxSize);
|
||||||
});
|
});
|
||||||
|
|
||||||
// weather values
|
// weather values
|
||||||
cityData.forEach(([, {x, y}, value, icon]) => {
|
cityData.forEach(([city, {x, y}, value, icon]) => {
|
||||||
const localX = props.offsetX + x * props.scale;
|
const localX = props.offsetX + x * props.scale;
|
||||||
const localY = props.offsetY + y * props.scale;
|
const localY = props.offsetY + y * props.scale;
|
||||||
|
|
||||||
@@ -47,9 +49,24 @@ export function drawOnMap(
|
|||||||
|
|
||||||
if (icon) {
|
if (icon) {
|
||||||
ctx.fillStyle = "#FFFFFF";
|
ctx.fillStyle = "#FFFFFF";
|
||||||
ctx.font = `normal ${props.fontSize * 5}px Daira by LTV grafika`;
|
ctx.font = `normal ${props.fontSize * (productionTemplate ? 4.2 : 5)}px Daira by LTV grafika`;
|
||||||
const iconSize = ctx.measureText(icon);
|
const iconMetrics = ctx.measureText(icon);
|
||||||
drawOpticallyCenteredGlyph(ctx, icon, localX + props.boxSize/2 + 10 + iconSize.width/2, localY);
|
const glyphWidth = iconMetrics.actualBoundingBoxLeft + iconMetrics.actualBoundingBoxRight;
|
||||||
|
const badgeWidth = getBadgeWidth(ctx, stringValue, props, true);
|
||||||
|
|
||||||
|
if (productionTemplate) {
|
||||||
|
const layout = faktiskaMarkerLayout[city as FaktiskaStation];
|
||||||
|
const side = layout?.iconSide ?? "right";
|
||||||
|
const gap = layout?.iconGap ?? 22;
|
||||||
|
const offsetX = layout?.iconOffsetX ?? 0;
|
||||||
|
const offsetY = layout?.iconOffsetY ?? 0;
|
||||||
|
const iconCenterX = side === "right"
|
||||||
|
? localX + badgeWidth / 2 + gap + glyphWidth / 2 + offsetX
|
||||||
|
: localX - badgeWidth / 2 - gap - glyphWidth / 2 + offsetX;
|
||||||
|
drawOpticallyCenteredGlyph(ctx, icon, iconCenterX, localY + offsetY);
|
||||||
|
} else {
|
||||||
|
drawOpticallyCenteredGlyph(ctx, icon, localX + props.boxSize / 2 + 10 + iconMetrics.width / 2, localY);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -81,6 +98,13 @@ export function drawOnMap(
|
|||||||
ctx.fillText("M/S", windProp.offsetX + boxMiddleX + gustsWidth / 2 - 22, (windProp.offsetY + 805)*windProp.scaleY);
|
ctx.fillText("M/S", windProp.offsetX + boxMiddleX + gustsWidth / 2 - 22, (windProp.offsetY + 805)*windProp.scaleY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getBadgeWidth(ctx: CanvasRenderingContext2D, value: string, props: ResolutionPropsValue, restoreValueFont = false): number {
|
||||||
|
if (restoreValueFont) ctx.font = `bold ${props.fontSize}px Rubik`;
|
||||||
|
const width = Math.max(props.boxSize, Math.ceil(ctx.measureText(value).width + props.fontSize * 0.8));
|
||||||
|
if (restoreValueFont) ctx.font = `normal ${props.fontSize * 4.2}px Daira by LTV grafika`;
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
function drawOpticallyCenteredGlyph(ctx: CanvasRenderingContext2D, glyph: string, centerX: number, centerY: number): void {
|
function drawOpticallyCenteredGlyph(ctx: CanvasRenderingContext2D, glyph: string, centerX: number, centerY: number): void {
|
||||||
const metrics = ctx.measureText(glyph);
|
const metrics = ctx.measureText(glyph);
|
||||||
ctx.textAlign = "left";
|
ctx.textAlign = "left";
|
||||||
|
|||||||
@@ -5,6 +5,38 @@ export const faktiskaStations = [
|
|||||||
|
|
||||||
export type FaktiskaStation = typeof faktiskaStations[number];
|
export type FaktiskaStation = typeof faktiskaStations[number];
|
||||||
|
|
||||||
|
export type FaktiskaMarkerLayout = {
|
||||||
|
iconSide: "left" | "right";
|
||||||
|
iconGap: number;
|
||||||
|
iconOffsetX: number;
|
||||||
|
iconOffsetY: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const rightMarker = (): FaktiskaMarkerLayout => ({
|
||||||
|
iconSide: "right",
|
||||||
|
iconGap: 22,
|
||||||
|
iconOffsetX: 0,
|
||||||
|
iconOffsetY: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
// These placements are part of the fixed newsroom template. Operators select
|
||||||
|
// the glyph; the renderer owns its position relative to the temperature badge.
|
||||||
|
export const faktiskaMarkerLayout: Record<FaktiskaStation, FaktiskaMarkerLayout> = {
|
||||||
|
"Liepāja": rightMarker(),
|
||||||
|
"Ventspils": rightMarker(),
|
||||||
|
"Stende": rightMarker(),
|
||||||
|
"Saldus": rightMarker(),
|
||||||
|
"Rīga": rightMarker(),
|
||||||
|
"Jelgava": rightMarker(),
|
||||||
|
"Ainaži": rightMarker(),
|
||||||
|
"Valmiera": rightMarker(),
|
||||||
|
"Madona": rightMarker(),
|
||||||
|
"Alūksne": rightMarker(),
|
||||||
|
"Zīlāni": rightMarker(),
|
||||||
|
"Daugavpils": rightMarker(),
|
||||||
|
"Rēzekne": rightMarker(),
|
||||||
|
};
|
||||||
|
|
||||||
export const faktiskaExport = {
|
export const faktiskaExport = {
|
||||||
width: 3840,
|
width: 3840,
|
||||||
height: 1440,
|
height: 1440,
|
||||||
|
|||||||
Reference in New Issue
Block a user