From e42ebb3e287e63085a10bc977e1830dae0bb6432 Mon Sep 17 00:00:00 2001 From: Jonathan Lurie Date: Thu, 29 Aug 2024 12:57:53 +0200 Subject: [PATCH] Rd 286: screenshots (#107) * Updated MapLibre version and adjusted MaptilerGeolocateControl * removed the zoomAdjust unnecesary value in minimap * Adding sky examples * Now passing the options to the control * Updated xmldom dep * Add screenshot helper * adding screenshot helper and readme * Adding whole page screenshot * removed an old async keyword * removed an old async keyword * removed an old async keyword * linting * adapt UMD bundle name to prod or dev mode * removing whole page screenshot --- package.json | 8 +++-- readme.md | 31 ++++++++++++++++++++ src/helpers/index.ts | 2 ++ src/helpers/screenshot.ts | 61 +++++++++++++++++++++++++++++++++++++++ vite.config-es.ts | 1 + vite.config-umd.ts | 4 ++- 6 files changed, 103 insertions(+), 4 deletions(-) create mode 100644 src/helpers/screenshot.ts diff --git a/package.json b/package.json index 433e54e2..1053d773 100644 --- a/package.json +++ b/package.json @@ -39,10 +39,12 @@ "doc": "rm -rf docs/* && typedoc --out docs && cp -r images docs/", "test": "vitest run", "build-css": "node scripts/replace-path-with-content.js src/style/style_template.css dist/tmp_maptiler-sdk.css && cat node_modules/maplibre-gl/dist/maplibre-gl.css dist/tmp_maptiler-sdk.css > dist/maptiler-sdk.css && rm dist/tmp_maptiler-sdk.css && cp dist/maptiler-sdk.css build/maptiler-sdk.css", - "build-umd": "NODE_ENV=production tsc && vite build -c vite.config-umd.ts", - "build-es": "NODE_ENV=production tsc && vite build -c vite.config-es.ts", + "build-umd": "tsc && NODE_ENV=production vite build -c vite.config-umd.ts", + "build-es": "tsc && NODE_ENV=production vite build -c vite.config-es.ts", "build": "npm run build-es; npm run build-umd; npm run build-css", - "make": "npm run biome:fix && npm run build" + "make": "npm run biome:fix && npm run build", + "dev-umd": "npm run build-css && tsc && NODE_ENV=dev vite build -w -c vite.config-umd.ts", + "help": "vite build --help" }, "author": "MapTiler", "devDependencies": { diff --git a/readme.md b/readme.md index 1be037ff..27758cc1 100644 --- a/readme.md +++ b/readme.md @@ -862,6 +862,37 @@ Turning off *zoom compensation* allows for more accurate adjustments to the visu All the other options are documented on [our reference page](https://docs.maptiler.com/sdk-js/api/helpers/#heatmap) and more examples are available [here](https://docs.maptiler.com/sdk-js/examples/?q=heatmap+helper). +# Other helper +## Take Screenshots, programmatically +There are two different ways to create screenshot, corresponding to two very different usecases. Note that screenshots will not contain *DOM elements* such as `Marker` and `Popup`, since those are not part of the rendering context. + +**1. Get a `blob` of a screenshot, PNG encoded:** +```ts +import { Map, helpers } from "@maptiler/sdk"; + +// ... initialize a Map instance, wait for the "load" or "ready" event ... + +// Inside an async function, or with using .then() +const blob = await helpers.takeScreenshot(map); +``` +The returned `Blob` of a PNG image file can be very handy if the goal is to programmatically further manipulate the screenshot, such as sending it to some feedback endpoint with a POST request. + +**2. Download a PNG file:** +```ts +import { Map, helpers } from "@maptiler/sdk"; + +// ... initialize a Map instance, wait for the "load" or "ready" event ... + +// No need to be inside an async function, the download will be triggered when the file is ready +maptilersdk.helpers.takeScreenshot(map, { + download: true, + filename: "map_screenshot.png" +}); +``` +Getting a file directly is a nice option that can be useful to share some debugging context with colleagues, compare multiple styles, or share your creation on social media. + +> 📣 *__Note:__* Keep in mind that MapTiler Cloud data are copyrighted and their usage is restricted. This include MapTiler built-in styles and tilesets, among others. In case of doubt, do not hesitate to read our [terms](https://www.maptiler.com/terms/) or to ask our [support team](https://www.maptiler.com/contacts/). + # Caching Starting from v2, MapTiler SDK introduced the **caching** of tiles and fonts served by MapTiler Cloud, which can represent a large chunk of the data being fetched when browsing a map. This caching leverages modern browsers caching API so it's well-managed and there is no risk of bloating! When we update **MapTiler Planet** or our **official styles**, the caching logic will detect it and automatically invalidate older versions of the tiles that were previously cached. diff --git a/src/helpers/index.ts b/src/helpers/index.ts index 9ce1dd3f..a7d26a5b 100644 --- a/src/helpers/index.ts +++ b/src/helpers/index.ts @@ -1,3 +1,4 @@ +import { takeScreenshot } from "./screenshot"; import { addPolyline, addPolygon, addPoint, addHeatmap } from "./vectorlayerhelpers"; export type { @@ -19,4 +20,5 @@ export const helpers = { addPolygon, addPoint, addHeatmap, + takeScreenshot, }; diff --git a/src/helpers/screenshot.ts b/src/helpers/screenshot.ts new file mode 100644 index 00000000..737cad6b --- /dev/null +++ b/src/helpers/screenshot.ts @@ -0,0 +1,61 @@ +import type { Map as MapSDK } from "../Map"; + +/** + * Takes a screenshot (PNG file) of the curent map view. + * Depending on the options, this function can automatically trigger a download of te file. + */ +export async function takeScreenshot( + map: MapSDK, + options: { + /** + * If `true`, this function will trigger a download in addition to returning a blob. + * Default: `false` + */ + download?: boolean; + + /** + * Only if `options.download` is `true`. Indicates the filename under which + * the file will be downloaded. + * Default: `"maptiler_screenshot.png"` + */ + filename?: string; + } = {}, +): Promise { + const download = options.download ?? false; + const blob = await getMapScreenshotBlob(map); + + if (download) { + const filename = options.filename ?? "maptiler_screenshot.png"; + + const link = document.createElement("a"); + link.style.display = "none"; + document.body.appendChild(link); + link.href = URL.createObjectURL(blob); + link.download = filename; + link.click(); + + // Cleaning after event loop + setTimeout(() => { + document.body.removeChild(link); + URL.revokeObjectURL(link.href); + }, 0); + } + + return blob; +} + +function getMapScreenshotBlob(map: MapSDK): Promise { + return new Promise((resolve, reject) => { + map.redraw(); + + map.once("idle", () => { + map.getCanvas().toBlob((blob) => { + if (!blob) { + return reject(Error("Screenshot could not be created.")); + } + + resolve(blob); + }, "image/png"); + }); + }); +} diff --git a/vite.config-es.ts b/vite.config-es.ts index 6437f927..67af3f29 100644 --- a/vite.config-es.ts +++ b/vite.config-es.ts @@ -12,6 +12,7 @@ export default defineConfig({ mode: isProduction ? "production" : "development", build: { minify: isProduction, + emptyOutDir: isProduction, outDir: "dist", sourcemap: true, lib: { diff --git a/vite.config-umd.ts b/vite.config-umd.ts index 3c658888..4dc5b9f2 100644 --- a/vite.config-umd.ts +++ b/vite.config-umd.ts @@ -2,6 +2,7 @@ import { resolve } from 'path'; import { defineConfig } from 'vite'; const isProduction = process.env.NODE_ENV === "production"; +const bundleFilename = isProduction ? "maptiler-sdk.umd.min.js" : "maptiler-sdk.umd.js" const plugins = []; @@ -11,13 +12,14 @@ export default defineConfig({ build: { outDir: "build", minify: isProduction, + emptyOutDir: isProduction, sourcemap: true, lib: { // Could also be a dictionary or array of multiple entry points entry: resolve(__dirname, 'src/index.ts'), name: 'maptilersdk', // the proper extensions will be added - fileName: (format, entryName) => "maptiler-sdk.umd.js", + fileName: (format, entryName) => bundleFilename, formats: ['umd'], } },