You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
topLat = "52,30",
bottomLat = "52,0",
leftLon = "13,0",
rightLon = "13,30",
latSuffix = "N",
lonSuffix = "E",
tickIntervalMin = 1,
labelIntervalMin = 10,
majorTickMultiplier = 2,
tickLength = 5,
frameWidth = 1,
margin = 50,
frameColor = "black",
textColor = "black",
fontSize = 12,
fontFace = "Roboto"
) {
// Helper to dynamically load Google Fonts if not web-safe
const loadFontIfNeeded = async (font, size) => {
const webSafeFonts = ["arial", "verdana", "tahoma", "trebuchet ms", "times new roman", "georgia", "garamond", "courier new", "brush script mt", "impact", "sans-serif", "serif", "monospace", "cursive", "fantasy"];
if (!webSafeFonts.includes(font.toLowerCase())) {
try {
const fontId = `google-font-${font.replace(/ /g, '-')}`;
if (!document.getElementById(fontId)) {
const link = document.createElement('link');
link.id = fontId;
link.href = `https://fonts.googleapis.com/css2?family=${font.replace(/ /g, '+')}:wght@400&display=swap`;
link.rel = 'stylesheet';
const p = new Promise((resolve, reject) => {
link.onload = resolve;
link.onerror = reject;
});
document.head.appendChild(link);
await p;
}
// Check if font is loaded
await document.fonts.load(`${size}px ${font}`);
return font;
} catch (e) {
console.warn(`Could not load font "${font}". Falling back to sans-serif.`, e);
return "sans-serif";
}
}
return font;
};
const effectiveFontFace = await loadFontIfNeeded(fontFace, fontSize);
// Helper function to parse 'DD,MM' string to total minutes
const parseCoord = (coordStr) => {
const parts = String(coordStr).split(',').map(s => parseFloat(s.trim()));
if (parts.length < 2 || parts.some(isNaN)) {
console.error(`Invalid coordinate format: "${coordStr}". Using 0,0.`);
return 0;
}
return parts[0] * 60 + parts[1];
};
// Helper function to format total minutes to 'DD° MM'' string
const formatCoord = (totalMinutes) => {
const degrees = Math.floor(totalMinutes / 60);
const minutes = (totalMinutes % 60).toFixed(0);
return `${degrees}° ${String(minutes).padStart(2, '0')}'`;
};
// Create canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const canvasWidth = originalImg.width + 2 * margin;
const canvasHeight = originalImg.height + 2 * margin;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// Draw a white background
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Draw the original image
ctx.drawImage(originalImg, margin, margin, originalImg.width, originalImg.height);
// Draw the frame
ctx.strokeStyle = frameColor;
ctx.lineWidth = frameWidth;
ctx.strokeRect(margin, margin, originalImg.width, originalImg.height);
// Calculate coordinate ranges and scales
const topLatMin = parseCoord(topLat);
const bottomLatMin = parseCoord(bottomLat);
const leftLonMin = parseCoord(leftLon);
const rightLonMin = parseCoord(rightLon);
const latSpanMin = topLatMin - bottomLatMin;
const lonSpanMin = rightLonMin - leftLonMin;
if (latSpanMin <= 0 || lonSpanMin <= 0) {
ctx.fillStyle = 'red';
ctx.font = '16px sans-serif';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText("Invalid coordinate range", canvasWidth / 2, canvasHeight / 2);
return canvas;
}
const pixelsPerLatMinute = originalImg.height / latSpanMin;
const pixelsPerLonMinute = originalImg.width / lonSpanMin;
// --- Draw Ticks and Labels ---
ctx.fillStyle = textColor;
ctx.font = `${fontSize}px ${effectiveFontFace}`;
ctx.strokeStyle = frameColor;
// Latitude Ticks and Labels (Left and Right)
for (let currentMin = Math.ceil(bottomLatMin / tickIntervalMin) * tickIntervalMin; currentMin <= topLatMin; currentMin += tickIntervalMin) {
const y = margin + originalImg.height - (currentMin - bottomLatMin) * pixelsPerLatMinute;
const isMajorTick = Math.abs(currentMin % labelIntervalMin) < 1e-9 || Math.abs((currentMin%labelIntervalMin) - labelIntervalMin) < 1e-9;
const currentTickLength = isMajorTick ? tickLength * majorTickMultiplier : tickLength;
// Draw left tick
ctx.lineWidth = isMajorTick ? frameWidth * 1.5 : frameWidth;
ctx.beginPath();
ctx.moveTo(margin, y);
ctx.lineTo(margin + currentTickLength, y);
ctx.stroke();
// Draw right tick
ctx.beginPath();
ctx.moveTo(margin + originalImg.width, y);
ctx.lineTo(margin + originalImg.width - currentTickLength, y);
ctx.stroke();
if (isMajorTick) {
const labelText = formatCoord(currentMin);
ctx.textAlign = 'right';
ctx.textBaseline = 'middle';
ctx.fillText(`${labelText} ${latSuffix}`, margin - 8, y);
ctx.textAlign = 'left';
ctx.fillText(`${labelText} ${latSuffix}`, margin + originalImg.width + 8, y);
}
}
// Longitude Ticks and Labels (Top and Bottom)
for (let currentMin = Math.ceil(leftLonMin / tickIntervalMin) * tickIntervalMin; currentMin <= rightLonMin; currentMin += tickIntervalMin) {
const x = margin + (currentMin - leftLonMin) * pixelsPerLonMinute;
const isMajorTick = Math.abs(currentMin % labelIntervalMin) < 1e-9 || Math.abs((currentMin%labelIntervalMin) - labelIntervalMin) < 1e-9;
const currentTickLength = isMajorTick ? tickLength * majorTickMultiplier : tickLength;
// Draw top tick
ctx.lineWidth = isMajorTick ? frameWidth * 1.5 : frameWidth;
ctx.beginPath();
ctx.moveTo(x, margin);
ctx.lineTo(x, margin + currentTickLength);
ctx.stroke();
// Draw bottom tick
ctx.beginPath();
ctx.moveTo(x, margin + originalImg.height);
ctx.lineTo(x, margin + originalImg.height - currentTickLength);
ctx.stroke();
if (isMajorTick) {
const labelText = formatCoord(currentMin);
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
ctx.fillText(`${labelText} ${lonSuffix}`, x, margin - 8);
ctx.textBaseline = 'top';
ctx.fillText(`${labelText} ${lonSuffix}`, x, margin + originalImg.height + 8);
}
}
return canvas;
}
Apply Changes