From 7ad403fa21c745b14013a1c228bd884d46187584 Mon Sep 17 00:00:00 2001 From: Craig Kochis Date: Wed, 25 Oct 2023 10:26:56 -0400 Subject: [PATCH] feat: use built-in svg marker (#2) * feat: use built-in svg marker Signed-off-by: Craig Kochis * Scaling based on screen resolution --------- Signed-off-by: Craig Kochis Co-authored-by: Boon Boonsiri --- src/serve_rendered.js | 39 ++++++++++++++++++++++++++++----------- src/utils.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 11 deletions(-) diff --git a/src/serve_rendered.js b/src/serve_rendered.js index da92fd6..5d2e7b0 100644 --- a/src/serve_rendered.js +++ b/src/serve_rendered.js @@ -23,6 +23,7 @@ import { getTileUrls, isValidHttpUrl, fixTileJSONCenter, + generateMarker, } from './utils.js'; import { PMtilesOpen, @@ -242,6 +243,10 @@ const parseMarkerOptions = (optionsList, marker) => { marker.offsetY = parseFloat(providedOffset[1]); } break; + // Custom color when using default marker + case 'color': + marker.color = String(optionParts[1]); + break; } } }; @@ -284,12 +289,16 @@ const extractMarkersFromQuery = (query, options, transformer) => { } let iconURI = markerParts[1]; + // Check if icon is served via http otherwise marker icons are expected to // be provided as filepaths relative to configured icon path - const isRemoteURL = - iconURI.startsWith('http://') || iconURI.startsWith('https://'); + const isDefaultMarker = iconURI === 'default'; + const isRemoteURL = iconURI.startsWith('http://') || iconURI.startsWith('https://'); const isDataURL = iconURI.startsWith('data:'); - if (!(isRemoteURL || isDataURL)) { + + if (isDefaultMarker) { + iconURI = 'default'; // use built-in marker + } else if (!(isRemoteURL || isDataURL)) { // Sanitize URI with sanitize-filename // https://www.npmjs.com/package/sanitize-filename#details iconURI = sanitize(iconURI); @@ -303,9 +312,9 @@ const extractMarkersFromQuery = (query, options, transformer) => { // When we encounter a remote icon check if the configuration explicitly allows them. } else if (isRemoteURL && options.allowRemoteMarkerIcons !== true) { - continue; + continue; // skip } else if (isDataURL && options.allowInlineMarkerImages !== true) { - continue; + continue; // skip } // Ensure marker location could be parsed @@ -347,11 +356,19 @@ const precisePx = (ll, zoom) => { * @param {object} marker Marker object parsed by extractMarkersFromQuery. * @param {number} z Map zoom level. */ -const drawMarker = (ctx, marker, z) => { - return new Promise((resolve) => { +const drawMarker = (ctx, marker, z, scale) => { + return new Promise(async (resolve) => { const img = new Image(); const pixelCoords = precisePx(marker.location, z); + let imgSrc = marker.icon; + if (marker.icon === 'default') { + // use default built-in marker (svg -> dataURL) + const markerDataURL = await generateMarker(scale, marker.color); + marker.scale = marker.scale ? marker.scale * (1 / scale) : 1 / scale; + imgSrc = markerDataURL; + } + const getMarkerCoordinates = (imageWidth, imageHeight, scale) => { // Images are placed with their top-left corner at the provided location // within the canvas but we expect icons to be centered and above it. @@ -402,10 +419,10 @@ const drawMarker = (ctx, marker, z) => { }; img.onload = drawOnCanvas; + img.src = imgSrc; img.onerror = (err) => { throw err; }; - img.src = marker.icon; }); }; @@ -418,12 +435,12 @@ const drawMarker = (ctx, marker, z) => { * @param {List[Object]} markers Marker objects parsed by extractMarkersFromQuery. * @param {number} z Map zoom level. */ -const drawMarkers = async (ctx, markers, z) => { +const drawMarkers = async (ctx, markers, z, scale) => { const markerPromises = []; for (const marker of markers) { // Begin drawing marker - markerPromises.push(drawMarker(ctx, marker, z)); + markerPromises.push(drawMarker(ctx, marker, z, scale)); } // Await marker drawings before continuing @@ -582,7 +599,7 @@ const renderOverlay = async ( }); // Await drawing of markers before rendering the canvas - await drawMarkers(ctx, markers, z); + await drawMarkers(ctx, markers, z, scale); return canvas.toBuffer(); }; diff --git a/src/utils.js b/src/utils.js index 123fed6..23a4fb0 100644 --- a/src/utils.js +++ b/src/utils.js @@ -3,6 +3,7 @@ import path from 'path'; import fs from 'node:fs'; import clone from 'clone'; +import sharp from 'sharp'; import glyphCompose from '@mapbox/glyph-pbf-composite'; /** @@ -174,3 +175,34 @@ export const isValidHttpUrl = (string) => { return url.protocol === 'http:' || url.protocol === 'https:'; }; + +// generate base64 data url for default marker +export const generateMarker = async (scale = 1, color = '#000257') => { + const markerSVG = ` + + + + + + + + + + + + + + + + `; + + let markerBuffer = Buffer.from(markerSVG); + if (scale > 1) { + markerBuffer = await sharp(markerBuffer) + .resize(30 * scale, 45 * scale) + .toBuffer(); + } + + const pngBuffer = await sharp(markerBuffer).png().toBuffer(); + return `data:image/png;base64,${pngBuffer.toString('base64')}`; +};