Please bookmark this page to avoid losing your image tool!

Image Map Frame Tick And Mark Replacer

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
You can edit the below JavaScript code to customize the image tool.
async function processImage(
    originalImg,
    minLon = 10.0,
    maxLon = 11.0,
    minLat = 52.0,
    maxLat = 53.0,
    intervalDeg = 0.25,
    frameWidth = 2,
    frameColor = 'black',
    tickLength = 10,
    tickColor = 'black',
    labelFont = '14px Arial',
    labelColor = 'black',
    labelPadding = 5
) {
    /**
     * Helper function to format decimal degrees into a DMS-like string.
     * @param {number} decimal The decimal degree value.
     * @param {'lat'|'lon'} type The type of coordinate ('lat' or 'lon').
     * @returns {string} The formatted coordinate string (e.g., "52°30'N").
     */
    function formatCoord(decimal, type) {
        const isLat = type === 'lat';
        let suffix = '';
        if (isLat) {
            suffix = decimal >= 0 ? 'N' : 'S';
        } else {
            suffix = decimal >= 0 ? 'E' : 'W';
        }

        const absDecimal = Math.abs(decimal);
        let degrees = Math.floor(absDecimal);
        let minutes = Math.round((absDecimal - degrees) * 60);

        if (minutes === 60) {
            minutes = 0;
            degrees += 1;
        }

        const minutesStr = String(minutes).padStart(2, '0');
        return `${degrees}°${minutesStr}'${suffix}`;
    }

    // Attempt to dynamically load Google Fonts if not a standard web-safe font.
    try {
        const fontNameMatch = labelFont.match(/(?:'|")([^'"]+)(?:'|")|([\w\s-]+)$/);
        if (fontNameMatch) {
            const fontName = (fontNameMatch[1] || fontNameMatch[2] || '').trim();
            if (fontName && !document.fonts.check(`1em "${fontName}"`)) {
                const fontUrl = `https://fonts.googleapis.com/css2?family=${fontName.replace(/ /g, '+')}&display=swap`;
                // Avoid adding duplicate link tags
                if (!document.querySelector(`link[href="${fontUrl}"]`)) {
                    const link = document.createElement('link');
                    link.rel = 'stylesheet';
                    link.href = fontUrl;
                    document.head.appendChild(link);
                }
                await document.fonts.load(`1em "${fontName}"`);
            }
        }
    } catch (e) {
        console.error("Font loading failed, continuing with browser default.", e);
    }


    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    ctx.font = labelFont;

    // Estimate the size of the labels to calculate necessary padding around the image.
    const testLabel = formatCoord(-179.999, 'lon'); // A long label like "180°00'W"
    const metrics = ctx.measureText(testLabel);
    const maxLabelWidth = metrics.width;
    const estimatedFontHeight = parseInt(labelFont.match(/(\d+)px/i)?.[1] || '14', 10);
    const fontHeight = (metrics.actualBoundingBoxAscent && metrics.actualBoundingBoxDescent) ?
        (metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent) :
        (estimatedFontHeight * 1.2); // Fallback with a multiplier

    const horizontalSpace = frameWidth / 2 + labelPadding + maxLabelWidth;
    const verticalSpace = frameWidth / 2 + labelPadding + fontHeight;

    canvas.width = originalImg.width + 2 * horizontalSpace;
    canvas.height = originalImg.height + 2 * verticalSpace;

    // Fill background (optional, but good practice)
    ctx.fillStyle = 'white';
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    const imgArea = {
        x: horizontalSpace,
        y: verticalSpace,
        width: originalImg.width,
        height: originalImg.height,
        right: horizontalSpace + originalImg.width,
        bottom: verticalSpace + originalImg.height
    };
    ctx.drawImage(originalImg, imgArea.x, imgArea.y);

    // Draw the main map frame
    ctx.strokeStyle = frameColor;
    ctx.lineWidth = frameWidth;
    ctx.strokeRect(imgArea.x, imgArea.y, imgArea.width, imgArea.height);

    // Prepare for drawing ticks and labels
    ctx.fillStyle = labelColor;
    ctx.strokeStyle = tickColor;
    ctx.lineWidth = 1; // Ticks are typically thin

    const lonSpan = maxLon - minLon;
    const latSpan = maxLat - minLat;

    // Draw longitude ticks and labels (Top/Bottom)
    if (lonSpan > 0 && intervalDeg > 0) {
        const lonSteps = Math.round(lonSpan / intervalDeg);
        for (let i = 0; i <= lonSteps; i++) {
            const lon = minLon + i * intervalDeg;
            const xPos = imgArea.x + (i / lonSteps) * imgArea.width;
            const label = formatCoord(lon, 'lon');

            // Top tick (points inward/down)
            ctx.beginPath();
            ctx.moveTo(xPos, imgArea.y);
            ctx.lineTo(xPos, imgArea.y + tickLength);
            ctx.stroke();

            // Bottom tick (points inward/up)
            ctx.beginPath();
            ctx.moveTo(xPos, imgArea.bottom);
            ctx.lineTo(xPos, imgArea.bottom - tickLength);
            ctx.stroke();

            // Top label
            ctx.textAlign = 'center';
            ctx.textBaseline = 'bottom';
            ctx.fillText(label, xPos, imgArea.y - frameWidth / 2 - labelPadding);

            // Bottom label
            ctx.textBaseline = 'top';
            ctx.fillText(label, xPos, imgArea.bottom + frameWidth / 2 + labelPadding);
        }
    }

    // Draw latitude ticks and labels (Left/Right)
    if (latSpan > 0 && intervalDeg > 0) {
        const latSteps = Math.round(latSpan / intervalDeg);
        for (let i = 0; i <= latSteps; i++) {
            const lat = minLat + i * intervalDeg;
            // Canvas Y is inverted, so proportion calculation is different
            const yPos = imgArea.bottom - (i / latSteps) * imgArea.height;
            const label = formatCoord(lat, 'lat');

            // Left tick (points inward/right)
            ctx.beginPath();
            ctx.moveTo(imgArea.x, yPos);
            ctx.lineTo(imgArea.x + tickLength, yPos);
            ctx.stroke();

            // Right tick (points inward/left)
            ctx.beginPath();
            ctx.moveTo(imgArea.right, yPos);
            ctx.lineTo(imgArea.right - tickLength, yPos);
            ctx.stroke();

            // Left label
            ctx.textAlign = 'right';
            ctx.textBaseline = 'middle';
            ctx.fillText(label, imgArea.x - frameWidth / 2 - labelPadding, yPos);

            // Right label
            ctx.textAlign = 'left';
            ctx.fillText(label, imgArea.right + frameWidth / 2 + labelPadding, yPos);
        }
    }

    return canvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The Image Map Frame Tick and Mark Replacer tool allows users to enhance images by adding a map frame with coordinate ticks and labels. This tool is particularly useful for creating visual representations of geographical data, such as in scientific presentations, educational materials, or analytical reports. Users can customize the latitude and longitude ranges, as well as the design elements like frame color, tick length, and font styles for labels. Ideal applications include mapping presentations, educational resources, and any scenario where location data needs to be visualized on an image.

Leave a Reply

Your email address will not be published. Required fields are marked *