tileserver-gl-light 5.5.0-pre.1 → 5.5.0-pre.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +51 -34
- package/docs/config.rst +52 -11
- package/docs/endpoints.rst +12 -2
- package/docs/installation.rst +6 -6
- package/docs/usage.rst +26 -0
- package/package.json +14 -14
- package/public/resources/elevation-control.js +92 -21
- package/public/resources/maplibre-gl-inspect.js +2827 -2770
- package/public/resources/maplibre-gl-inspect.js.map +1 -1
- package/public/resources/maplibre-gl.css +1 -1
- package/public/resources/maplibre-gl.js +4 -4
- package/public/resources/maplibre-gl.js.map +1 -1
- package/src/main.js +31 -20
- package/src/pmtiles_adapter.js +104 -45
- package/src/promises.js +1 -1
- package/src/render.js +270 -93
- package/src/serve_data.js +266 -90
- package/src/serve_font.js +2 -2
- package/src/serve_light.js +2 -4
- package/src/serve_rendered.js +445 -236
- package/src/serve_style.js +29 -8
- package/src/server.js +115 -60
- package/src/utils.js +47 -20
- package/test/elevation.js +513 -0
- package/test/fixtures/visual/encoded-path-auto.png +0 -0
- package/test/fixtures/visual/linecap-linejoin-bevel-square.png +0 -0
- package/test/fixtures/visual/linecap-linejoin-round-round.png +0 -0
- package/test/fixtures/visual/path-auto.png +0 -0
- package/test/fixtures/visual/static-bbox.png +0 -0
- package/test/fixtures/visual/static-bearing-pitch.png +0 -0
- package/test/fixtures/visual/static-bearing.png +0 -0
- package/test/fixtures/visual/static-border-global.png +0 -0
- package/test/fixtures/visual/static-lat-lng.png +0 -0
- package/test/fixtures/visual/static-markers.png +0 -0
- package/test/fixtures/visual/static-multiple-paths.png +0 -0
- package/test/fixtures/visual/static-path-border-isolated.png +0 -0
- package/test/fixtures/visual/static-path-border-stroke.png +0 -0
- package/test/fixtures/visual/static-path-latlng.png +0 -0
- package/test/fixtures/visual/static-pixel-ratio-2x.png +0 -0
- package/test/static_images.js +241 -0
- package/test/tiles_data.js +1 -1
- package/test/utils/create_terrain_mbtiles.js +124 -0
package/src/render.js
CHANGED
|
@@ -5,6 +5,17 @@ import { SphericalMercator } from '@mapbox/sphericalmercator';
|
|
|
5
5
|
|
|
6
6
|
const mercator = new SphericalMercator();
|
|
7
7
|
|
|
8
|
+
// Constants
|
|
9
|
+
const CONSTANTS = {
|
|
10
|
+
DEFAULT_LINE_WIDTH: 1,
|
|
11
|
+
DEFAULT_BORDER_WIDTH_RATIO: 0.1, // 10% of line width
|
|
12
|
+
DEFAULT_FILL_COLOR: 'rgba(255,255,255,0.4)',
|
|
13
|
+
DEFAULT_STROKE_COLOR: 'rgba(0,64,255,0.7)',
|
|
14
|
+
MAX_LINE_WIDTH: 500,
|
|
15
|
+
MAX_BORDER_WIDTH: 250,
|
|
16
|
+
MARKER_LOAD_TIMEOUT: 5000,
|
|
17
|
+
};
|
|
18
|
+
|
|
8
19
|
/**
|
|
9
20
|
* Transforms coordinates to pixels.
|
|
10
21
|
* @param {Array<number>} ll - Longitude/Latitude coordinate pair.
|
|
@@ -17,6 +28,73 @@ const precisePx = (ll, zoom) => {
|
|
|
17
28
|
return [px[0] * scale, px[1] * scale];
|
|
18
29
|
};
|
|
19
30
|
|
|
31
|
+
/**
|
|
32
|
+
* Validates if a string is a valid color value.
|
|
33
|
+
* @param {string} color - Color string to validate.
|
|
34
|
+
* @returns {boolean} True if valid color.
|
|
35
|
+
*/
|
|
36
|
+
const isValidColor = (color) => {
|
|
37
|
+
if (!color || typeof color !== 'string') {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Allow 'none' and 'transparent' keywords
|
|
42
|
+
if (color === 'none' || color === 'transparent') {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Basic validation for common formats
|
|
47
|
+
const hexPattern = /^#([0-9A-Fa-f]{3}){1,2}$/; // 3 or 6 digits
|
|
48
|
+
const hexAlphaPattern = /^#([0-9A-Fa-f]{8})$/; // 8 digits with alpha
|
|
49
|
+
const rgbPattern = /^rgb\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*\)$/;
|
|
50
|
+
const rgbaPattern = /^rgba\(\s*\d+\s*,\s*\d+\s*,\s*\d+\s*,\s*[\d.]+\s*\)$/;
|
|
51
|
+
const namedColors = [
|
|
52
|
+
'red',
|
|
53
|
+
'blue',
|
|
54
|
+
'green',
|
|
55
|
+
'yellow',
|
|
56
|
+
'black',
|
|
57
|
+
'white',
|
|
58
|
+
'gray',
|
|
59
|
+
'grey',
|
|
60
|
+
'orange',
|
|
61
|
+
'purple',
|
|
62
|
+
'pink',
|
|
63
|
+
'brown',
|
|
64
|
+
'cyan',
|
|
65
|
+
'magenta',
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
return (
|
|
69
|
+
hexPattern.test(color) ||
|
|
70
|
+
hexAlphaPattern.test(color) ||
|
|
71
|
+
rgbPattern.test(color) ||
|
|
72
|
+
rgbaPattern.test(color) ||
|
|
73
|
+
namedColors.includes(color.toLowerCase())
|
|
74
|
+
);
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Safely parses a numeric value with bounds checking.
|
|
79
|
+
* @param {string|number} value - Value to parse.
|
|
80
|
+
* @param {number} defaultValue - Default value if parsing fails.
|
|
81
|
+
* @param {number} min - Minimum allowed value.
|
|
82
|
+
* @param {number} max - Maximum allowed value.
|
|
83
|
+
* @returns {number} Parsed and bounded value.
|
|
84
|
+
*/
|
|
85
|
+
const safeParseNumber = (
|
|
86
|
+
value,
|
|
87
|
+
defaultValue,
|
|
88
|
+
min = -Infinity,
|
|
89
|
+
max = Infinity,
|
|
90
|
+
) => {
|
|
91
|
+
const parsed = Number(value);
|
|
92
|
+
if (isNaN(parsed)) {
|
|
93
|
+
return defaultValue;
|
|
94
|
+
}
|
|
95
|
+
return Math.max(min, Math.min(max, parsed));
|
|
96
|
+
};
|
|
97
|
+
|
|
20
98
|
/**
|
|
21
99
|
* Draws a marker in canvas context.
|
|
22
100
|
* @param {CanvasRenderingContext2D} ctx - Canvas context object.
|
|
@@ -25,22 +103,28 @@ const precisePx = (ll, zoom) => {
|
|
|
25
103
|
* @returns {Promise<void>} A promise that resolves when the marker is drawn.
|
|
26
104
|
*/
|
|
27
105
|
const drawMarker = (ctx, marker, z) => {
|
|
28
|
-
return new Promise((resolve) => {
|
|
106
|
+
return new Promise((resolve, reject) => {
|
|
29
107
|
const img = new Image();
|
|
30
108
|
const pixelCoords = precisePx(marker.location, z);
|
|
31
109
|
|
|
110
|
+
// Add timeout to prevent hanging on slow/failed image loads
|
|
111
|
+
const timeout = setTimeout(() => {
|
|
112
|
+
reject(new Error(`Marker image load timeout: ${marker.icon}`));
|
|
113
|
+
}, CONSTANTS.MARKER_LOAD_TIMEOUT);
|
|
114
|
+
|
|
32
115
|
const getMarkerCoordinates = (imageWidth, imageHeight, scale) => {
|
|
33
116
|
// Images are placed with their top-left corner at the provided location
|
|
34
117
|
// within the canvas but we expect icons to be centered and above it.
|
|
35
118
|
|
|
36
|
-
//
|
|
119
|
+
// Subtract half of the image's width from the x-coordinate to center
|
|
37
120
|
// the image in relation to the provided location
|
|
38
121
|
let xCoordinate = pixelCoords[0] - imageWidth / 2;
|
|
39
|
-
|
|
122
|
+
|
|
123
|
+
// Subtract the image's height from the y-coordinate to place it above
|
|
40
124
|
// the provided location
|
|
41
125
|
let yCoordinate = pixelCoords[1] - imageHeight;
|
|
42
126
|
|
|
43
|
-
// Since image placement is dependent on the size offsets have to be
|
|
127
|
+
// Since image placement is dependent on the size, offsets have to be
|
|
44
128
|
// scaled as well. Additionally offsets are provided as either positive or
|
|
45
129
|
// negative values so we always add them
|
|
46
130
|
if (marker.offsetX) {
|
|
@@ -57,30 +141,38 @@ const drawMarker = (ctx, marker, z) => {
|
|
|
57
141
|
};
|
|
58
142
|
|
|
59
143
|
const drawOnCanvas = () => {
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
144
|
+
clearTimeout(timeout);
|
|
145
|
+
|
|
146
|
+
try {
|
|
147
|
+
// Check if the image should be resized before being drawn
|
|
148
|
+
const defaultScale = 1;
|
|
149
|
+
const scale = marker.scale ? marker.scale : defaultScale;
|
|
150
|
+
|
|
151
|
+
// Calculate scaled image sizes
|
|
152
|
+
const imageWidth = img.width * scale;
|
|
153
|
+
const imageHeight = img.height * scale;
|
|
154
|
+
|
|
155
|
+
// Pass the desired sizes to get correlating coordinates
|
|
156
|
+
const coords = getMarkerCoordinates(imageWidth, imageHeight, scale);
|
|
157
|
+
|
|
158
|
+
// Draw the image on canvas
|
|
159
|
+
if (scale !== defaultScale) {
|
|
160
|
+
ctx.drawImage(img, coords.x, coords.y, imageWidth, imageHeight);
|
|
161
|
+
} else {
|
|
162
|
+
ctx.drawImage(img, coords.x, coords.y);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Resolve the promise when image has been drawn
|
|
166
|
+
resolve();
|
|
167
|
+
} catch (error) {
|
|
168
|
+
reject(new Error(`Failed to draw marker: ${error.message}`));
|
|
76
169
|
}
|
|
77
|
-
// Resolve the promise when image has been drawn
|
|
78
|
-
resolve();
|
|
79
170
|
};
|
|
80
171
|
|
|
81
172
|
img.onload = drawOnCanvas;
|
|
82
|
-
img.onerror = (
|
|
83
|
-
|
|
173
|
+
img.onerror = () => {
|
|
174
|
+
clearTimeout(timeout);
|
|
175
|
+
reject(new Error(`Failed to load marker image: ${marker.icon}`));
|
|
84
176
|
};
|
|
85
177
|
img.src = marker.icon;
|
|
86
178
|
});
|
|
@@ -89,7 +181,7 @@ const drawMarker = (ctx, marker, z) => {
|
|
|
89
181
|
/**
|
|
90
182
|
* Draws a list of markers onto a canvas.
|
|
91
183
|
* Wraps drawing of markers into list of promises and awaits them.
|
|
92
|
-
* It's required because images are expected to load
|
|
184
|
+
* It's required because images are expected to load asynchronously in canvas js
|
|
93
185
|
* even when provided from a local disk.
|
|
94
186
|
* @param {CanvasRenderingContext2D} ctx - Canvas context object.
|
|
95
187
|
* @param {Array<object>} markers - Marker objects parsed by extractMarkersFromQuery.
|
|
@@ -105,7 +197,26 @@ const drawMarkers = async (ctx, markers, z) => {
|
|
|
105
197
|
}
|
|
106
198
|
|
|
107
199
|
// Await marker drawings before continuing
|
|
108
|
-
|
|
200
|
+
// Use Promise.allSettled to continue even if some markers fail
|
|
201
|
+
const results = await Promise.allSettled(markerPromises);
|
|
202
|
+
|
|
203
|
+
// Log any failures
|
|
204
|
+
results.forEach((result, index) => {
|
|
205
|
+
if (result.status === 'rejected') {
|
|
206
|
+
console.warn(`Marker ${index} failed to render:`, result.reason);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Extracts an option value from a path query string.
|
|
213
|
+
* @param {Array<string>} splitPaths - Path string split by pipe character.
|
|
214
|
+
* @param {string} optionName - Name of the option to extract.
|
|
215
|
+
* @returns {string|undefined} Option value or undefined if not found.
|
|
216
|
+
*/
|
|
217
|
+
const getInlineOption = (splitPaths, optionName) => {
|
|
218
|
+
const found = splitPaths.find((x) => x.startsWith(`${optionName}:`));
|
|
219
|
+
return found ? found.replace(`${optionName}:`, '') : undefined;
|
|
109
220
|
};
|
|
110
221
|
|
|
111
222
|
/**
|
|
@@ -118,21 +229,25 @@ const drawMarkers = async (ctx, markers, z) => {
|
|
|
118
229
|
* @returns {void}
|
|
119
230
|
*/
|
|
120
231
|
const drawPath = (ctx, path, query, pathQuery, z) => {
|
|
121
|
-
const splitPaths = pathQuery.split('|');
|
|
122
|
-
|
|
123
232
|
if (!path || path.length < 2) {
|
|
124
|
-
return
|
|
233
|
+
return;
|
|
125
234
|
}
|
|
126
235
|
|
|
236
|
+
const splitPaths = pathQuery.split('|');
|
|
237
|
+
|
|
238
|
+
// Start the path - transform coordinates to pixels on canvas and draw lines between points
|
|
127
239
|
ctx.beginPath();
|
|
128
240
|
|
|
129
|
-
|
|
130
|
-
for (const pair of path) {
|
|
241
|
+
for (const [i, pair] of path.entries()) {
|
|
131
242
|
const px = precisePx(pair, z);
|
|
132
|
-
|
|
243
|
+
if (i === 0) {
|
|
244
|
+
ctx.moveTo(px[0], px[1]);
|
|
245
|
+
} else {
|
|
246
|
+
ctx.lineTo(px[0], px[1]);
|
|
247
|
+
}
|
|
133
248
|
}
|
|
134
249
|
|
|
135
|
-
// Check if first coordinate matches last coordinate
|
|
250
|
+
// Check if first coordinate matches last coordinate (closed path)
|
|
136
251
|
if (
|
|
137
252
|
path[0][0] === path[path.length - 1][0] &&
|
|
138
253
|
path[0][1] === path[path.length - 1][1]
|
|
@@ -140,77 +255,130 @@ const drawPath = (ctx, path, query, pathQuery, z) => {
|
|
|
140
255
|
ctx.closePath();
|
|
141
256
|
}
|
|
142
257
|
|
|
143
|
-
//
|
|
144
|
-
const
|
|
258
|
+
// --- FILL Logic ---
|
|
259
|
+
const inlineFill = getInlineOption(splitPaths, 'fill');
|
|
260
|
+
const pathHasFill = inlineFill !== undefined;
|
|
261
|
+
|
|
145
262
|
if (query.fill !== undefined || pathHasFill) {
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
263
|
+
let fillColor;
|
|
264
|
+
|
|
149
265
|
if (pathHasFill) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
266
|
+
fillColor = inlineFill;
|
|
267
|
+
} else if ('fill' in query) {
|
|
268
|
+
fillColor = query.fill || CONSTANTS.DEFAULT_FILL_COLOR;
|
|
269
|
+
} else {
|
|
270
|
+
fillColor = CONSTANTS.DEFAULT_FILL_COLOR;
|
|
153
271
|
}
|
|
154
|
-
ctx.fill();
|
|
155
|
-
}
|
|
156
272
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
273
|
+
// Validate color before using
|
|
274
|
+
if (isValidColor(fillColor)) {
|
|
275
|
+
ctx.fillStyle = fillColor;
|
|
276
|
+
ctx.fill();
|
|
277
|
+
} else {
|
|
278
|
+
console.warn(`Invalid fill color: ${fillColor}, using default`);
|
|
279
|
+
ctx.fillStyle = CONSTANTS.DEFAULT_FILL_COLOR;
|
|
280
|
+
ctx.fill();
|
|
165
281
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// --- WIDTH & BORDER Logic ---
|
|
285
|
+
const inlineWidth = getInlineOption(splitPaths, 'width');
|
|
286
|
+
const pathHasWidth = inlineWidth !== undefined;
|
|
287
|
+
const inlineBorder = getInlineOption(splitPaths, 'border');
|
|
288
|
+
const inlineBorderWidth = getInlineOption(splitPaths, 'borderwidth');
|
|
289
|
+
const pathHasBorder = inlineBorder !== undefined;
|
|
290
|
+
|
|
291
|
+
// Parse line width with validation
|
|
292
|
+
let lineWidth = CONSTANTS.DEFAULT_LINE_WIDTH;
|
|
293
|
+
if (pathHasWidth) {
|
|
294
|
+
lineWidth = safeParseNumber(
|
|
295
|
+
inlineWidth,
|
|
296
|
+
CONSTANTS.DEFAULT_LINE_WIDTH,
|
|
297
|
+
0,
|
|
298
|
+
CONSTANTS.MAX_LINE_WIDTH,
|
|
299
|
+
);
|
|
300
|
+
} else if ('width' in query) {
|
|
301
|
+
lineWidth = safeParseNumber(
|
|
302
|
+
query.width,
|
|
303
|
+
CONSTANTS.DEFAULT_LINE_WIDTH,
|
|
304
|
+
0,
|
|
305
|
+
CONSTANTS.MAX_LINE_WIDTH,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Get border width with validation
|
|
310
|
+
// Default: 10% of line width
|
|
311
|
+
let borderWidth = lineWidth * CONSTANTS.DEFAULT_BORDER_WIDTH_RATIO;
|
|
312
|
+
if (pathHasBorder && inlineBorderWidth) {
|
|
313
|
+
borderWidth = safeParseNumber(
|
|
314
|
+
inlineBorderWidth,
|
|
315
|
+
borderWidth,
|
|
316
|
+
0,
|
|
317
|
+
CONSTANTS.MAX_BORDER_WIDTH,
|
|
318
|
+
);
|
|
319
|
+
} else if (query.borderwidth !== undefined) {
|
|
320
|
+
borderWidth = safeParseNumber(
|
|
321
|
+
query.borderwidth,
|
|
322
|
+
borderWidth,
|
|
323
|
+
0,
|
|
324
|
+
CONSTANTS.MAX_BORDER_WIDTH,
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Set rendering style for the start and end points of the path
|
|
329
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineCap
|
|
330
|
+
const validLineCaps = ['butt', 'round', 'square'];
|
|
331
|
+
ctx.lineCap = validLineCaps.includes(query.linecap) ? query.linecap : 'butt';
|
|
332
|
+
|
|
333
|
+
// Set rendering style for overlapping segments of the path with differing directions
|
|
334
|
+
// https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineJoin
|
|
335
|
+
const validLineJoins = ['miter', 'round', 'bevel'];
|
|
336
|
+
ctx.lineJoin = validLineJoins.includes(query.linejoin)
|
|
337
|
+
? query.linejoin
|
|
338
|
+
: 'miter';
|
|
339
|
+
|
|
340
|
+
// The final border color, prioritized by inline over global query
|
|
341
|
+
const finalBorder = pathHasBorder ? inlineBorder : query.border;
|
|
342
|
+
|
|
343
|
+
// In order to simulate a border we draw the path two times with the first
|
|
344
|
+
// being the wider border part.
|
|
345
|
+
if (finalBorder !== undefined && borderWidth > 0) {
|
|
346
|
+
// Validate border color
|
|
347
|
+
if (isValidColor(finalBorder)) {
|
|
189
348
|
// We need to double the desired border width and add it to the line width
|
|
190
349
|
// in order to get the desired border on each side of the line.
|
|
191
350
|
ctx.lineWidth = lineWidth + borderWidth * 2;
|
|
192
|
-
|
|
193
|
-
ctx.strokeStyle = query.border;
|
|
351
|
+
ctx.strokeStyle = finalBorder;
|
|
194
352
|
ctx.stroke();
|
|
353
|
+
} else {
|
|
354
|
+
console.warn(`Invalid border color: ${finalBorder}, skipping border`);
|
|
195
355
|
}
|
|
196
|
-
ctx.lineWidth = lineWidth;
|
|
197
356
|
}
|
|
198
357
|
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
358
|
+
// Set line width for the main stroke
|
|
359
|
+
ctx.lineWidth = lineWidth;
|
|
360
|
+
|
|
361
|
+
// --- STROKE Logic ---
|
|
362
|
+
const inlineStroke = getInlineOption(splitPaths, 'stroke');
|
|
363
|
+
const pathHasStroke = inlineStroke !== undefined;
|
|
364
|
+
|
|
365
|
+
let strokeColor;
|
|
366
|
+
if (pathHasStroke) {
|
|
367
|
+
strokeColor = inlineStroke;
|
|
368
|
+
} else if ('stroke' in query) {
|
|
369
|
+
strokeColor = query.stroke;
|
|
211
370
|
} else {
|
|
212
|
-
|
|
371
|
+
strokeColor = CONSTANTS.DEFAULT_STROKE_COLOR;
|
|
213
372
|
}
|
|
373
|
+
|
|
374
|
+
// Validate stroke color
|
|
375
|
+
if (isValidColor(strokeColor)) {
|
|
376
|
+
ctx.strokeStyle = strokeColor;
|
|
377
|
+
} else {
|
|
378
|
+
console.warn(`Invalid stroke color: ${strokeColor}, using default`);
|
|
379
|
+
ctx.strokeStyle = CONSTANTS.DEFAULT_STROKE_COLOR;
|
|
380
|
+
}
|
|
381
|
+
|
|
214
382
|
ctx.stroke();
|
|
215
383
|
};
|
|
216
384
|
|
|
@@ -260,23 +428,32 @@ export const renderOverlay = async (
|
|
|
260
428
|
const canvas = createCanvas(scale * w, scale * h);
|
|
261
429
|
const ctx = canvas.getContext('2d');
|
|
262
430
|
ctx.scale(scale, scale);
|
|
431
|
+
|
|
263
432
|
if (bearing) {
|
|
264
433
|
ctx.translate(w / 2, h / 2);
|
|
265
434
|
ctx.rotate((-bearing / 180) * Math.PI);
|
|
266
435
|
ctx.translate(-center[0], -center[1]);
|
|
267
436
|
} else {
|
|
268
|
-
//
|
|
437
|
+
// Optimized path
|
|
269
438
|
ctx.translate(-center[0] + w / 2, -center[1] + h / 2);
|
|
270
439
|
}
|
|
271
440
|
|
|
272
441
|
// Draw provided paths if any
|
|
273
442
|
paths.forEach((path, i) => {
|
|
274
443
|
const pathQuery = Array.isArray(query.path) ? query.path.at(i) : query.path;
|
|
275
|
-
|
|
444
|
+
try {
|
|
445
|
+
drawPath(ctx, path, query, pathQuery, z);
|
|
446
|
+
} catch (error) {
|
|
447
|
+
console.error(`Error drawing path ${i}:`, error);
|
|
448
|
+
}
|
|
276
449
|
});
|
|
277
450
|
|
|
278
451
|
// Await drawing of markers before rendering the canvas
|
|
279
|
-
|
|
452
|
+
try {
|
|
453
|
+
await drawMarkers(ctx, markers, z);
|
|
454
|
+
} catch (error) {
|
|
455
|
+
console.error('Error drawing markers:', error);
|
|
456
|
+
}
|
|
280
457
|
|
|
281
458
|
return canvas.toBuffer();
|
|
282
459
|
};
|