aur-openlayers 19.6.11 → 19.6.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.
@@ -211,6 +211,114 @@ function collectLayersExtent(layers, layerIds) {
211
211
  return isEmpty(extent) ? null : extent;
212
212
  }
213
213
 
214
+ /**
215
+ * Композитит canvas'ы ОДНОГО viewport карты в офскрин-canvas заданного размера.
216
+ *
217
+ * Берёт только `.ol-layer canvas` внутри переданного `viewport` (а не из всего
218
+ * документа), применяя per-canvas `opacity` родителя и матрицу `style.transform`
219
+ * по образцу официального примера экспорта OpenLayers. Размер результата всегда
220
+ * равен `[width, height]`; high-DPR backing слоёв ужимается в него через матрицу.
221
+ */
222
+ function compositeViewport(viewport, [width, height], opts = {}) {
223
+ const out = document.createElement('canvas');
224
+ out.width = width;
225
+ out.height = height;
226
+ const ctx = out.getContext('2d');
227
+ if (opts.background) {
228
+ ctx.fillStyle = opts.background;
229
+ ctx.fillRect(0, 0, width, height);
230
+ }
231
+ const canvases = viewport.querySelectorAll('.ol-layer canvas, canvas.ol-layer');
232
+ canvases.forEach((canvas) => {
233
+ if (canvas.width === 0 || canvas.height === 0)
234
+ return;
235
+ const parent = canvas.parentNode;
236
+ const opacity = parent?.style.opacity || canvas.style.opacity;
237
+ ctx.globalAlpha = opacity === '' ? 1 : Number(opacity);
238
+ const transform = canvas.style.transform;
239
+ const match = transform ? transform.match(/^matrix\(([^)]+)\)$/) : null;
240
+ const matrix = match
241
+ ? match[1].split(',').map(Number)
242
+ : [
243
+ (parseFloat(canvas.style.width) || canvas.width) / canvas.width,
244
+ 0,
245
+ 0,
246
+ (parseFloat(canvas.style.height) || canvas.height) / canvas.height,
247
+ 0,
248
+ 0,
249
+ ];
250
+ ctx.setTransform(matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]);
251
+ const bgColor = parent?.style.backgroundColor;
252
+ if (bgColor) {
253
+ ctx.fillStyle = bgColor;
254
+ ctx.fillRect(0, 0, canvas.width, canvas.height);
255
+ }
256
+ ctx.drawImage(canvas, 0, 0);
257
+ });
258
+ ctx.globalAlpha = 1;
259
+ ctx.setTransform(1, 0, 0, 1, 0, 0);
260
+ return out;
261
+ }
262
+
263
+ /**
264
+ * Создаёт функцию `exportImage` для конкретной карты.
265
+ *
266
+ * Параллельные вызовы сериализуются (общий мутируемый `map`): каждый следующий
267
+ * экспорт стартует только после завершения предыдущего. Цикл одного экспорта:
268
+ * snapshot → setSize → fit → ожидание `rendercomplete` → композит → restore.
269
+ *
270
+ * Инвариант: подписка `map.once('rendercomplete')` и последующие
271
+ * `setSize`/`fit`/`render` выполняются синхронно (никаких `await` до подписки),
272
+ * иначе следующий экспорт мог бы поймать `rendercomplete` от restore-рендера
273
+ * предыдущего.
274
+ */
275
+ function createExportImage(map) {
276
+ let tail = Promise.resolve();
277
+ const doExport = async (options) => {
278
+ const view = map.getView();
279
+ const size0 = map.getSize();
280
+ if (!size0) {
281
+ throw new Error('exportImage: map has no size yet (is it added to the DOM?)');
282
+ }
283
+ const resolution0 = view.getResolution();
284
+ const center0 = view.getCenter();
285
+ const extent0 = view.calculateExtent(size0);
286
+ const { size, format = 'image/png', quality = 0.92, background, fit = 'currentExtent', } = options;
287
+ try {
288
+ const rendered = new Promise((resolve) => {
289
+ map.once('rendercomplete', () => resolve());
290
+ });
291
+ map.setSize(size);
292
+ if (fit === 'currentExtent') {
293
+ view.fit(extent0, { size });
294
+ }
295
+ else {
296
+ if (resolution0 != null)
297
+ view.setResolution(resolution0);
298
+ if (center0)
299
+ view.setCenter(center0);
300
+ }
301
+ map.render(); // гарантия кадра даже при size === size0
302
+ await rendered;
303
+ const canvas = compositeViewport(map.getViewport(), size, { background });
304
+ return canvas.toDataURL(format, quality);
305
+ }
306
+ finally {
307
+ map.setSize(size0);
308
+ if (resolution0 != null)
309
+ view.setResolution(resolution0);
310
+ if (center0)
311
+ view.setCenter(center0);
312
+ map.render();
313
+ }
314
+ };
315
+ return (options) => {
316
+ const run = tail.then(() => doExport(options));
317
+ tail = run.catch(() => { }); // очередь переживает ошибку одного экспорта
318
+ return run;
319
+ };
320
+ }
321
+
214
322
  const createMapContext = (map, layers, popupHost, scheduler = new FlushScheduler()) => {
215
323
  return {
216
324
  map,
@@ -227,6 +335,7 @@ const createMapContext = (map, layers, popupHost, scheduler = new FlushScheduler
227
335
  if (extent)
228
336
  map.getView().fit(extent, toOlFitOptions(opts, map));
229
337
  },
338
+ exportImage: createExportImage(map),
230
339
  };
231
340
  };
232
341