mobility-toolbox-js
Version:
Toolbox for JavaScript applications in the domains of mobility and logistics.
291 lines (290 loc) • 10.6 kB
JavaScript
import createCanvas from '../utils/createCanvas';
const cacheDelayBg = {};
/**
* Draw circle delay background
*
* @private
*/
export const getDelayBgCanvas = (origin, radius, color) => {
const key = `${origin}, ${radius}, ${color}`;
if (!cacheDelayBg[key]) {
const canvas = createCanvas(origin * 2, origin * 2);
if (canvas) {
const ctx = canvas.getContext('2d');
if (!ctx) {
return null;
}
ctx.beginPath();
ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = color;
ctx.filter = 'blur(1px)';
ctx.fill();
cacheDelayBg[key] = canvas;
}
}
return cacheDelayBg[key];
};
const cacheDelayText = {};
/**
* Draw delay text
*
* @private
*/
export const getDelayTextCanvas = (text, fontSize, font, delayColor, delayOutlineColor = '#000', pixelRatio = 1) => {
const key = `${text}, ${font}, ${delayColor}, ${delayOutlineColor}, ${pixelRatio}`;
if (!cacheDelayText[key]) {
const canvas = createCanvas(Math.ceil(text.length * fontSize), Math.ceil(fontSize + 8 * pixelRatio));
if (canvas) {
const ctx = canvas.getContext('2d');
if (!ctx) {
return null;
}
ctx.font = font;
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.font = font;
ctx.fillStyle = delayColor;
ctx.strokeStyle = delayOutlineColor;
ctx.lineWidth = 1.5 * pixelRatio;
ctx.strokeText(text, 0, fontSize);
ctx.fillText(text, 0, fontSize);
cacheDelayText[key] = canvas;
}
}
return cacheDelayText[key];
};
const cacheCircle = {};
/**
* Draw colored circle with black border
*
* @private
*/
export const getCircleCanvas = (origin, radius, color, hasStroke, hasDash, pixelRatio) => {
const key = `${origin}, ${radius}, ${color}, ${hasStroke}, ${hasDash}, ${pixelRatio}`;
if (!cacheCircle[key]) {
const canvas = createCanvas(origin * 2, origin * 2);
if (canvas) {
const ctx = canvas.getContext('2d');
if (!ctx) {
return null;
}
ctx.fillStyle = color;
if (hasStroke) {
ctx.lineWidth = 1 * pixelRatio;
ctx.strokeStyle = '#000000';
}
ctx.beginPath();
ctx.arc(origin, origin, radius, 0, 2 * Math.PI, false);
ctx.fill();
if (hasDash) {
ctx.setLineDash([5, 3]);
}
if (hasStroke) {
ctx.stroke();
}
cacheCircle[key] = canvas;
}
}
return cacheCircle[key];
};
const cacheText = {};
/**
* Draw text in the circle
*
* @private
*/
export const getTextCanvas = (text, origin, textSize, fillColor, strokeColor, hasStroke, pixelRatio, getTextFont) => {
const key = `${text}, ${origin}, ${textSize}, ${fillColor},${strokeColor}, ${hasStroke}, ${pixelRatio}`;
if (!cacheText[key]) {
const canvas = createCanvas(origin * 2, origin * 2);
if (canvas) {
const ctx = canvas.getContext('2d');
if (!ctx) {
return null;
}
// Draw a stroke to the text only if a provider provides realtime but we don't use it.
if (hasStroke) {
ctx.save();
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.font = getTextFont(textSize + 2, text);
ctx.strokeStyle = strokeColor;
ctx.strokeText(text, origin, origin);
ctx.restore();
}
// Draw a text
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
ctx.fillStyle = fillColor;
ctx.font = getTextFont(textSize, text);
ctx.strokeStyle = strokeColor;
ctx.strokeText(text, origin, origin);
ctx.fillText(text, origin, origin);
cacheText[key] = canvas;
}
}
return cacheText[key];
};
const cache = {};
/**
* A tracker style that take in account the delay.
*
* @param {RealtimeTrajectory} trajectory The trajectory to render.
* @param {ViewState} viewState The view state of the map.
* @param {RealtimeStyleOptions} options Some options to change the rendering
* @return a canvas
* @private
*/
const realtimeDefaultStyle = (trajectory, viewState, options) => {
const { delayDisplay = 300000, delayOutlineColor = '#000', getBgColor = () => {
return '#000';
}, getDelayColor = () => {
return '#000';
}, getDelayFont = (fontSize) => {
return `bold ${fontSize}px arial, sans-serif`;
}, getDelayText = () => {
return null;
}, getMaxRadiusForStrokeAndDelay = () => {
return 7;
}, getMaxRadiusForText = () => {
return 10;
}, getRadius = () => {
return 0;
}, getText = (text) => {
return text;
}, getTextColor = () => {
return '#000';
}, getTextFont = (fontSize) => {
return `bold ${fontSize}px arial, sans-serif`;
}, getTextSize = () => {
return 14;
}, hoverVehicleId, selectedVehicleId, useDelayStyle, } = options;
const { pixelRatio = 1, zoom } = viewState;
let { type } = trajectory.properties;
const { delay, line, operator_provides_realtime_journey: operatorProvidesRealtime, state, train_id: id, } = trajectory.properties;
let { color, name, text_color: textColor } = line || {};
name = getText(name);
const cancelled = state === 'JOURNEY_CANCELLED';
if (!type) {
type = 'rail';
}
if (!name) {
name = 'I';
}
if (color && !color.startsWith('#')) {
color = `#${color}`;
}
if (textColor && !textColor.startsWith('#')) {
textColor = `#${textColor}`;
}
const z = Math.min(Math.floor(zoom || 1), 16);
const hover = !!(hoverVehicleId && hoverVehicleId === id);
const selected = !!(selectedVehicleId && selectedVehicleId === id);
// Calcul the radius of the circle
let radius = getRadius(type, z) * pixelRatio;
const isDisplayStrokeAndDelay = radius >= getMaxRadiusForStrokeAndDelay() * pixelRatio;
if (hover || selected) {
radius = isDisplayStrokeAndDelay
? radius + 5 * pixelRatio
: 14 * pixelRatio;
}
const isDisplayText = radius > getMaxRadiusForText() * pixelRatio;
// Optimize the cache key, very important in high zoom level
let key = `${radius}${hover || selected}`;
if (useDelayStyle) {
key += `${operatorProvidesRealtime}${delay}${cancelled}`;
}
else {
color = color || getBgColor(type, line);
key += `${color}`;
if (isDisplayStrokeAndDelay) {
key += `${cancelled}${delay}`;
}
}
if (isDisplayText) {
// Get the text color of the vehicle
if (useDelayStyle) {
textColor = '#000000';
}
else {
textColor = textColor || getTextColor(type, line);
}
key += `${name}${textColor}`;
}
if (!cache[key]) {
if (radius === 0) {
return null;
}
const margin = 1 * pixelRatio;
const radiusDelay = radius + 2 * pixelRatio;
const markerSize = radius * 2;
const size = radiusDelay * 2 + margin * 2;
const origin = size / 2;
// Draw circle delay background
let delayBg = null;
if (isDisplayStrokeAndDelay && delay !== null) {
delayBg = getDelayBgCanvas(origin, radiusDelay, getDelayColor(delay, cancelled));
}
// Show delay if feature is hovered or if delay is above 5mins.
let delayText = null;
let fontSize = 0;
if (isDisplayStrokeAndDelay &&
(hover || (delay || 0) >= delayDisplay || cancelled)) {
// Draw delay text
fontSize =
Math.max(cancelled ? 19 : 14, Math.min(cancelled ? 19 : 17, radius * 1.2)) * pixelRatio;
const text = getDelayText(delay, cancelled);
if (text) {
delayText = getDelayTextCanvas(text, fontSize, getDelayFont(fontSize, text), getDelayColor(delay, cancelled, true), delayOutlineColor, pixelRatio);
}
}
const hasStroke = isDisplayStrokeAndDelay || hover || selected;
const hasDash = !!isDisplayStrokeAndDelay &&
!!useDelayStyle &&
delay === null &&
operatorProvidesRealtime === 'yes';
// Get the color of the vehicle
let circleFillColor = color || '#fff';
if (useDelayStyle) {
circleFillColor = getDelayColor(delay, cancelled);
}
const circle = getCircleCanvas(origin, radius, circleFillColor, hasStroke, hasDash, pixelRatio);
// Create the canvas
const width = size + ((delayText === null || delayText === void 0 ? void 0 : delayText.width) || 0) * 2;
const height = size;
const canvas = createCanvas(width, height);
if (canvas) {
const ctx = canvas.getContext('2d');
if (!ctx) {
return null;
}
// The renderTrajectories will center the image on the vehicle positions.
const originX = (delayText === null || delayText === void 0 ? void 0 : delayText.width) || 0;
if (delayBg) {
ctx.drawImage(delayBg, originX, 0);
}
if (circle) {
ctx.drawImage(circle, originX, 0);
}
// Draw text in the circle
let circleText = null;
if (isDisplayText) {
const fontSize2 = Math.max(radius, 10);
const textSize = getTextSize(ctx, markerSize, name, fontSize2, getTextFont);
const hasStroke2 = !!useDelayStyle &&
delay === null &&
operatorProvidesRealtime === 'yes';
circleText = getTextCanvas(name, origin, textSize, textColor || '#000', circleFillColor, hasStroke2, pixelRatio, getTextFont);
}
if (circleText) {
ctx.drawImage(circleText, originX, 0);
}
if (delayText) {
ctx.drawImage(delayText, originX + Math.ceil(origin + radiusDelay) + margin, Math.ceil(origin - fontSize));
}
cache[key] = canvas;
}
}
return cache[key];
};
export default realtimeDefaultStyle;