Please bookmark this page to avoid losing your image tool!

Image Map Degree And Minutes Indicator Enhancer

(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,
    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;
}

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 Degree and Minutes Indicator Enhancer is a tool designed to enhance images by overlaying detailed latitude and longitude indicators. Users can input coordinate ranges, customize labels, and adjust visual elements like colors, tick intervals, and font styles. This tool is especially useful for cartographers, educators, or anyone working with geographical maps, enabling them to create informative visual aids that show precise coordinates directly on images. By utilizing this tool, users can effectively present and analyze spatial data in a visually engaging manner.

Leave a Reply

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