Skip to main content
Glama
mapService.ts7.63 kB
/* * Copyright (C) 2025 TomTom NV * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { tomtomClient, validateApiKey, API_VERSION } from "../base/tomtomClient"; import { VERSION } from "../../version"; import { logger } from "../../utils/logger"; import { fetchCopyrightCaption, addCopyrightOverlay } from "../../utils/copyrightUtils"; import { MapOptions, DEFAULT_MAP_OPTIONS } from "./types"; // Canvas import will be done dynamically when needed let createCanvas: any; let canvasLoadAttempted = false; /** * Dynamically import canvas if available */ async function loadCanvasIfAvailable() { if (canvasLoadAttempted) { return createCanvas !== undefined; } canvasLoadAttempted = true; try { const canvasModule = await import("canvas"); createCanvas = canvasModule.createCanvas; return true; } catch (error) { logger.warn("⚠️ Canvas library not available: copyright overlay will be skipped for static maps"); return false; } } /** * Get a static map image * @param options Map display options * @returns URL to the static map image */ export function getStaticMapUrl(options: MapOptions): string { validateApiKey(); // Apply default values const width = options.width || DEFAULT_MAP_OPTIONS.width; const height = options.height || DEFAULT_MAP_OPTIONS.height; const style = options.style || DEFAULT_MAP_OPTIONS.style; const layer = options.layer || DEFAULT_MAP_OPTIONS.layer; const view = options.view || DEFAULT_MAP_OPTIONS.view; // Geopolitical view const format = options.format || DEFAULT_MAP_OPTIONS.format; // png has better quality and supports transparency const baseUrl = tomtomClient.defaults.baseURL || ""; const apiVersion = API_VERSION.MAP || 1; // Default to version 1 if not defined // Validate dimensions (1-8192 according to documentation) if (width < 1 || width > 8192 || height < 1 || height > 8192) { throw new Error("Width and height must be between 1 and 8192 pixels"); } // Start building the URL - tomtomClient will automatically append the API key let url = `/map/${apiVersion}/staticimage?`; // Either center+zoom or bbox must be provided if (options.center) { const zoom = options.zoom || DEFAULT_MAP_OPTIONS.zoom; // Validate zoom level (0-22 according to documentation) if (zoom < 0 || zoom > 22) { throw new Error("Zoom level must be between 0 and 22"); } url += `center=${options.center.lon},${options.center.lat}&zoom=${zoom}`; logger.debug( `Generated static map URL with center: (${options.center.lat}, ${options.center.lon}), zoom: ${zoom}` ); } else if (options.bbox && options.bbox.length === 4) { // Bounding box format: [west, south, east, north] url += `bbox=${options.bbox[0]},${options.bbox[1]},${options.bbox[2]},${options.bbox[3]}`; logger.debug(`Generated static map URL with bbox: [${options.bbox.join(", ")}]`); } else { throw new Error("Either center coordinates or bounding box must be provided"); } // Add required parameters url += `&format=${format}` + `&layer=${layer}` + `&style=${style}` + `&width=${width}` + `&height=${height}` + `&view=${view}`; // Note: The Static Image API doesn't directly support markers through the URL. // If markers are needed, client-side rendering should be used or consider // implementing a separate solution such as: // 1. Use a server-side image processing library to add markers to the image // 2. Use the Vector Map API with a custom style that includes markers // 3. Implement client-side marker rendering on top of the static image // Add optional parameters if (options.language) { url += `&language=${options.language}`; } return url; } /** * Get a static map image and return it as base64-encoded data * @param options Map display options * @returns Promise resolving to the image as base64-encoded data */ export async function getStaticMapImage( options: MapOptions ): Promise<{ base64: string; contentType: string }> { try { // Get the URL first const mapUrl = getStaticMapUrl(options); // Log the URL before making the request logger.info(`Making static map request to: ${tomtomClient.defaults.baseURL}${mapUrl}`); // Use tomtomClient to download the image (it automatically includes proper headers) const response = await tomtomClient.get(mapUrl, { responseType: "arraybuffer", }); // Log the actual URL that was requested (including any modifications made by axios) if (response.config && response.config.url) { logger.info(`Full request URL was: ${response.config.url}`); } if (!response.status || response.status >= 400) { throw new Error(`Failed to fetch map image: HTTP ${response.status}`); } // Get the content type from the response headers const contentType = response.headers?.["content-type"] || "image/png"; // The image data is already in the response const imageBuffer = response.data; // Add copyright overlay to the static map image let finalImageBuffer = imageBuffer; // Try to load Canvas dynamically const canvasAvailable = await loadCanvasIfAvailable(); if (canvasAvailable) { try { // Fetch Genesis copyright text (static maps are Genesis only) const copyrightText = await fetchCopyrightCaption(false); logger.info(`📄 Static map copyright text: ${copyrightText}`); // Get image dimensions from options const width = options.width || DEFAULT_MAP_OPTIONS.width; const height = options.height || DEFAULT_MAP_OPTIONS.height; // Create canvas and load the original image const canvas = createCanvas(width, height); const ctx = canvas.getContext('2d'); // Load and draw the original image const buffer = Buffer.from(imageBuffer); const { loadImage } = await import('canvas'); const img = await loadImage(buffer); ctx.drawImage(img, 0, 0, width, height); // Add copyright overlay using shared utility addCopyrightOverlay(ctx, copyrightText, width, height); // Convert canvas back to buffer finalImageBuffer = canvas.toBuffer('image/png'); logger.debug(`Added copyright overlay to static map image`); } catch (overlayError: any) { logger.error(`Failed to add copyright overlay to static map: ${overlayError.message}. Using original image.`); // Use original image if overlay fails finalImageBuffer = imageBuffer; } } else { logger.debug('Canvas not available, skipping copyright overlay for static map'); } // Convert the buffer to base64 const base64 = Buffer.from(finalImageBuffer).toString("base64"); logger.debug(`Downloaded and processed static map image (${(finalImageBuffer.byteLength / 1024).toFixed(2)} KB)`); return { base64, contentType }; } catch (error) { logger.error(`Failed to download static map: ${error}`); throw error; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/tomtom-international/tomtom-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server