{"id":28577000,"url":"https://github.com/maptiler/maptiler-sdk-js","last_synced_at":"2026-05-08T03:12:19.930Z","repository":{"id":65920228,"uuid":"537101113","full_name":"maptiler/maptiler-sdk-js","owner":"maptiler","description":"Maps SDK tailored for MapTiler Cloud powered by MapLibre GL JS","archived":false,"fork":false,"pushed_at":"2026-05-01T13:56:35.000Z","size":184959,"stargazers_count":125,"open_issues_count":13,"forks_count":24,"subscribers_count":8,"default_branch":"main","last_synced_at":"2026-05-01T15:32:24.860Z","etag":null,"topics":["geocoding","map","maplibre","maplibre-gl-js","maps","maptiler","webmap"],"latest_commit_sha":null,"homepage":"https://docs.maptiler.com/sdk-js/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"bsd-3-clause","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/maptiler.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2022-09-15T15:55:42.000Z","updated_at":"2026-04-30T08:46:37.000Z","dependencies_parsed_at":"2023-10-13T11:53:00.067Z","dependency_job_id":"d67d8330-5f0f-4d26-8e35-ed7754a3d1d0","html_url":"https://github.com/maptiler/maptiler-sdk-js","commit_stats":{"total_commits":111,"total_committers":2,"mean_commits":55.5,"dds":"0.018018018018018056","last_synced_commit":"477d058e0c9d4a93ce85cfd320a31db27612df21"},"previous_names":[],"tags_count":129,"template":false,"template_full_name":null,"purl":"pkg:github/maptiler/maptiler-sdk-js","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-sdk-js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-sdk-js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-sdk-js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-sdk-js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maptiler","download_url":"https://codeload.github.com/maptiler/maptiler-sdk-js/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-sdk-js/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32764812,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-08T02:36:36.067Z","status":"ssl_error","status_checked_at":"2026-05-08T02:36:07.210Z","response_time":54,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["geocoding","map","maplibre","maplibre-gl-js","maps","maptiler","webmap"],"created_at":"2025-06-11T00:08:40.256Z","updated_at":"2026-05-08T03:12:19.882Z","avatar_url":"https://github.com/maptiler.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg src=\"images/maptiler-sdk-logo.svg\" alt=\"Company Logo\" height=\"32\"/\u003e\n\n# MapTiler SDK JS\n\nThe **MapTiler SDK JS** extends MapLibre GL JS, exposes all its features, and adds new ones on top. The SDK is designed to work with the well-established [MapTiler service](https://www.maptiler.com/cloud/), which provides all the data required to fuel a complete web mapping experience: vector tiles, satellite raster tiles, DEM with Terrain RGB, custom styles with an editor, etc.\n\n**Why are we creating a new SDK?** To make things simpler for developers working in the MapTiler ecosystem! With **MapTiler SDK JS**, there is no need to load external plugins for the most basic things, copy-paste complex data source URLs, or look up the syntax to enable 3D terrain every time you start a project. All this is built-in, loaded when needed, or exposed with simple functions. Under the hood, this SDK is opinionated as it's being fed by [MapTiler service](https://www.maptiler.com/cloud/) data, but its MapLibre core makes it 100% compatible with other sources.\n\nIn addition, the MapTiler SDK JS provides well-documented and easy-to-use wrapper functions to the [MapTiler API services](https://docs.maptiler.com/cloud/api) such as: geocoding, static maps, geolocation, as well as a search engine for coordinate reference systems and transforming coordinates from one CRS to another.\n\n\u003e 📣 _**Note:**_ If you need \u003cins\u003eonly the API Client library\u003c/ins\u003e to use in a headless fashion and without any map display, check out [MapTiler Client JS](https://github.com/maptiler/maptiler-client-js) library for browser and NodeJS.\n\n[![npm](https://img.shields.io/npm/v/@maptiler/sdk?style=for-the-badge\u0026labelColor=D3DBEC\u0026color=f2f6ff\u0026logo=npm\u0026logoColor=333359)](https://www.npmjs.com/package/@maptiler/sdk) ![](https://img.shields.io/badge/-white?style=for-the-badge\u0026logo=javascript)![](https://img.shields.io/badge/-white?style=for-the-badge\u0026logo=typescript)\n\n---\n\n📖 [Documentation](https://docs.maptiler.com/sdk-js/) \u0026nbsp; 📦 [NPM Package](https://www.npmjs.com/package/@maptiler/sdk) \u0026nbsp; 🌐 [Website](https://www.maptiler.com/interactive-maps/) \u0026nbsp; 🔑 [Get API Key](https://cloud.maptiler.com/account/keys/)\n\n---\n\n\u003cbr\u003e\n\n\u003cdetails\u003e \u003csummary\u003e\u003cb\u003eTable of Contents\u003c/b\u003e\u003c/summary\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"#-installation\"\u003eInstallation\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-basic-usage\"\u003eBasic Usage\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-related-examples\"\u003eExamples\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-api-reference\"\u003eAPI Reference\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#migration-guide\"\u003eMigration Guide\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-support\"\u003eSupport\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-contributing\"\u003eContributing\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-license\"\u003eLicense\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"#-acknowledgements\"\u003eAcknowledgements\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/details\u003e\n\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"images/screenshots/lake-louise.jpg\" alt=\"Demo Screenshot\" width=\"80%\"/\u003e\n\u003cbr /\u003e\n\u003ca href=\"https://docs.maptiler.com/sdk-js/examples/ts-get-started/\"\u003eSee live interactive demo\u003c/a\u003e \n\u003c/p\u003e\n\u003cbr\u003e\n\n## 📦 Installation\n\n```shell\nnpm install --save @maptiler/sdk\n```\n\n⚠️ Please keep in mind that if you use any additional [MapTiler modules](https://docs.maptiler.com/sdk-js/modules/), you must update them to a version that supports MapTiler SDK JS v3.\n\nFrom CDN and using the UMD bundle, in the `\u003chead\u003e\u003c/head\u003e` section of your HTML file:\n\n```html\n\u003cscript src=\"https://cdn.maptiler.com/maptiler-sdk-js/\u003cVERSION\u003e/maptiler-sdk.umd.min.js\"\u003e\u003c/script\u003e\n\u003clink href=\"https://cdn.maptiler.com/maptiler-sdk-js/\u003cVERSION\u003e/maptiler-sdk.css\" rel=\"stylesheet\" /\u003e\n```\n\n\u003cbr\u003e\n\n## 🚀 Basic Usage\n\n### With ES modules\n\n**Recommended for:** advanced applications\n\n```ts\nimport { config, Map } from \"@maptiler/sdk\";\n\n// Add your MapTiler Cloud API key to the config\n// (Go to https://cloud.maptiler.com/account/keys/ to get one for free!)\nconfig.apiKey = \"YOUR_API_KEY\";\n\n// Let's say you have a DIV ready to receive a map\nconst mapContainer = document.getElementById(\"my-container-div\");\n\n// Instantiate the map\nconst map = new Map({\n  container: mapContainer,\n});\n```\n\nAlternatively, the `apiKey` can be set as Map option instead of in the `config` object. Yet, this will still internally propagate to the `config` object:\n\n```ts\nimport { Map } from \"@maptiler/sdk\";\n\n// Let's say you have a DIV ready to receive a map\nconst mapContainer = document.getElementById(\"my-container-div\");\n\n// Instantiate the map\nconst map = new Map({\n  container: mapContainer,\n  apiKey: \"YOUR_API_KEY\",\n});\n```\n\nBy default, the map will be initialized with the style [streets-v2](https://www.maptiler.com/maps/#style=streets-v2).\n\nDepending on the framework and environment you are using for your application, you will have to also include the CSS file.\n\nFor example, with a [NextJS](https://nextjs.org/) app, this can take place at the top of the file `_app.ts/js`:\n\n```ts\nimport \"@maptiler/sdk/dist/maptiler-sdk.css\";\n```\n\n#### TypeScript\n\nThe SDK is fully typed, but it may happen that types defined in Maplibre GL JS are not visible in your project. This is a known issue that comes from Maplibre being a CommonJS bundle.\n\nThere are mainly two ways to address this issue and access to the complete type definition.\n\n1. **With `esModuleInterop`**\n\nSet the following in your `tsconfig.json`:\n\n```js\n{\n  \"compilerOptions\": {\n    // ...\n    \"esModuleInterop\": true,\n  }\n}\n```\n\n2. **With `moduleResolution`**\n\nSet the following in your `tsconfig.json`:\n\n```js\n{\n  \"compilerOptions\": {\n    // ...\n    \"moduleResolution\": \"Bundler\",\n  }\n}\n```\n\nNote that this second option is not always possible as some frameworks and other dependencies won't let you use the \"Bundler\" mode.\n\n### With CDN\n\nThe SDK hosted on our CDN is bundled as _[Universal Module Definition](https://github.com/umdjs/umd)_ (UMD) to make it standalone and contain all its dependencies. The CDN also serves the style sheet (CSS).\n\n**Recommended for:** simple map integration example and demos\n\n```html\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003ctitle\u003eMapTiler JS SDK example\u003c/title\u003e\n    \u003cstyle\u003e\n      html,\n      body {\n        margin: 0;\n      }\n\n      #map-container {\n        position: absolute;\n        width: 100vw;\n        height: 100vh;\n      }\n    \u003c/style\u003e\n\n    \u003c!-- Load the SDK CSS --\u003e\n    \u003clink rel=\"stylesheet\" href=\"dist/maptiler-sdk.css\" /\u003e\n  \u003c/head\u003e\n\n  \u003cbody\u003e\n    \u003cdiv id=\"map-container\"\u003e\u003c/div\u003e\n\n    \u003cscript src=\"dist/maptiler-sdk.umd.min.js\"\u003e\u003c/script\u003e\n\n    \u003cscript\u003e\n      // Add your MapTiler API key to the config\n      // (Go to https://cloud.maptiler.com/account/keys/ to get one for free!)\n      maptilersdk.config.apiKey = \"YOUR_API_KEY\";\n\n      const mapContainer = document.getElementById(\"my-container-div\");\n\n      const map = new maptilersdk.Map({\n        container: mapContainer,\n        style: maptilersdk.MapStyle.STREETS_DARK,\n        hash: true,\n      });\n    \u003c/script\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n\nCheck out the minimalist code samples in the [demos](demos) directory.\n\n\u003cbr\u003e\n\n## 💡 Related Examples\n\n- [How to use the MapTiler SDK JS](https://docs.maptiler.com/sdk-js/examples/how-to-use/)\n- [Maps SDK with TypeScript](https://docs.maptiler.com/sdk-js/examples/ts-get-started/)\n- [Built-in map styles](https://docs.maptiler.com/sdk-js/examples/built-in-styles/)\n\nCheck out more than 200 [examples and tutorials](https://docs.maptiler.com/sdk-js/examples/)\n\n\u003cbr\u003e\n\n## 📘 API Reference\n\nIn addition to the details and examples provided in this readme, check out the [complete API documentation](https://docs.maptiler.com/sdk-js/api/)\n\n### Many styles to choose from\n\nMapTiler teams maintain a few styles that we have decided to expose from the SDK. This has two advantages:\n\n- they are easier to remember, no need to type along style URL\n- if we make an update to a style, you will benefit from it without modifying your codebase\n\nHere is how it works:\n\n```ts\nimport { Map, MapStyle } from \"@maptiler/sdk\";\n\n// When instanciating a map\nconst map = new Map({\n  container: document.getElementById(\"my-container-div\"),\n  style: MapStyle.OUTDOOR, // \u003c-- the shorthand for the outdoor style\n});\n\n// Or later on, updating the style\nmap.setStyle(MapStyle.STREETS.DARK);\n```\n\nThe styles with a shorthand provided by the SDK are the following:\n\n|           ID            |                                                                 Screenshot                                                                 |                                                                 Comment                                                                 |\n| :---------------------: | :----------------------------------------------------------------------------------------------------------------------------------------: | :-------------------------------------------------------------------------------------------------------------------------------------: |\n|   `MapStyle.STREETS`    | [![](images/screenshots/style-streets-v2.jpeg)](https://www.maptiler.com/maps/#style=streets-v2\u0026mode=2d\u0026position=12.52/40.73676/-73.98418) |                The classic default style, perfect for urban areas.\u003cp\u003eAlso available in **dark** and **light** mode.\u003c/p\u003e                 |\n| `MapStyle.DATAVIZ.DARK` |   [![](images/screenshots/style-dataviz-dark.jpeg)](https://www.maptiler.com/maps/#style=dataviz-dark\u0026mode=2d\u0026position=2.01/38.7/-27.0)    |                     A minimalist style for data visualization.\u003cp\u003eAlso available in **color** and **light** mode\u003c/p\u003e                     |\n|  `MapStyle.SATELLITE`   |      [![](images/screenshots/style-satellite.jpeg)](https://www.maptiler.com/maps/#style=hybrid\u0026mode=2d\u0026position=7.87/24.518/-77.411)      |                                     Only high resolution satellite raster tiles without any labels                                      |\n|    `MapStyle.HYBRID`    |       [![](images/screenshots/style-hybrid.jpeg)](https://www.maptiler.com/maps/#style=hybrid\u0026mode=2d\u0026position=9.4/-26.175/122.6631)       |                                 Satellite tile with labels, landmarks, roads ways and political borders                                 |\n|   `MapStyle.OUTDOOR`    |     [![](images/screenshots/style-outdoor.jpeg)](https://www.maptiler.com/maps/#style=outdoor\u0026mode=2d\u0026position=11.96/46.02591/7.7273)      |                                     A solid hiking companion, with peaks, parks, isolines and more                                      |\n|    `MapStyle.BASIC`     |  [![](images/screenshots/style-basic-v2.jpeg)](https://www.maptiler.com/maps/#style=basic-v2\u0026mode=2d\u0026position=13.09/37.78734/-122.42025)   | A minimalist alternative to `STREETS`, with a touch of flat design.\u003cp\u003eAlso available in **dark** and **light** and **pastel** mode.\u003c/p\u003e |\n\n\u003cdetails\u003e\n  \u003csummary\u003eKnow more about built-in map styles\u003c/summary\u003e\n\nMapTiler provides some **reference styles** as well as some **variants** for each. A **reference style** sets some guidelines about what kind of information is displayed, the granularity of the information, and more generally defines a purpose for which this style is the most relevant: street navigation, outdoor adventure, minimalist dashboard, etc. Then, each **reference style** offers a range of **variants** that contain the same level of information and have the same purpose but use different color schemes.\n\nHere is the full list:\n\n- `MapStyle.STREETS`, reference style for navigation and city exploration\n  - `MapStyle.STREETS.DARK` (variant)\n  - `MapStyle.STREETS.LIGHT` (variant)\n  - `MapStyle.STREETS.PASTEL` (variant)\n- `MapStyle.SATELLITE` reference style satellite and airborne imagery (no variants)\n- `MapStyle.HYBRID` reference style satellite and airborne imagery with labels (no variants)\n- `MapStyle.OUTDOOR` reference style for adventure\n- `MapStyle.WINTER` reference style for winter adventure\n- `MapStyle.DATAVIZ`, the perfect style for data visualization, with very little noise\n  - `MapStyle.DATAVIZ.DARK` (variant)\n  - `MapStyle.DATAVIZ.LIGHT` (variant)\n- `MapStyle.BACKDROP`, great style for data visualization when hillshading matters!\n  - `MapStyle.BACKDROP.DARK` (variant)\n  - `MapStyle.BACKDROP.LIGHT` (variant)\n- `MapStyle.BASIC` reference style for minimalist design and general purpose\n  - `MapStyle.BASIC.DARK` (variant)\n  - `MapStyle.BASIC.LIGHT` (variant)\n- `MapStyle.BRIGHT` reference style for high-contrast navigation\n  - `MapStyle.BRIGHT.DARK` (variant)\n  - `MapStyle.BRIGHT.LIGHT` (variant)\n  - `MapStyle.BRIGHT.PASTEL` (variant)\n- `MapStyle.TOPO` reference style for topographic study\n  - `MapStyle.TOPO.SHINY` (variant)\n  - `MapStyle.TOPO.PASTEL` (variant)\n  - `MapStyle.TOPO.TOPOGRAPHIQUE` (variant)\n- `MapStyle.VOYAGER` reference style for stylish yet minimalist maps\n  - `MapStyle.VOYAGER.DARK` (variant)\n  - `MapStyle.VOYAGER.LIGHT` (variant)\n  - `MapStyle.VOYAGER.VINTAGE` (variant)\n- `MapStyle.TONER` reference style for very high contrast stylish maps\n  - `MapStyle.TONER.BACKGROUND` (variant)\n  - `MapStyle.TONER.LITE` (variant)\n  - `MapStyle.TONER.LINES` (variant)\n- `MapStyle.OCEAN` reference style with bathymetric highlights, does not have any variants.\n- `MapStyle.LANDSCAPE` reference style terrain map for data overlays and visualisations\n  - `MapStyle.LANDSCAPE.DARK` (variant)\n  - `MapStyle.LANDSCAPE.VIVID` (variant)\n- `MapStyle.AQUARELLE` reference style watercolor map for creative use\n  - `MapStyle.AQUARELLE.DARK` (variant)\n  - `MapStyle.AQUARELLE.VIVID` (variant)\n- `MapStyle.OPENSTREETMAP` (reference style, this one does not have any variants)\n\nAll reference styles (instances of `ReferenceMapStyle`) and style variants (instances of `MapStyleVariant`) have methods to know the alternative styles and variants that belong to the same reference style (`.getVariants()`). This is handy to provide a default/dark/light alternative color scheme, yet preserving the same level of details as in the reference style. Read more about about [ReferenceMapStyle](https://docs.maptiler.com/sdk-js/api/map-styles/#referencemapstyle) and [MapStyleVariant](https://docs.maptiler.com/sdk-js/api/map-styles/#mapstylevariant).\n\n\u003c/details\u003e\n\n---\n\nYou can also use classic styles with just a _string_ if you know their MapTiler ID:\n\n```ts\nmap.setStyle(\"outdoor-v2\");\n```\n\nAnd finally, you can use your own custom styles designed with [our style editor](https://cloud.maptiler.com/maps/). Every custom style is given a unique ID, for instance: `c912ffc8-2360-487a-973b-59d037fb15b8`.\n\nThis ID can be provided as such:\n\n```ts\nmap.setStyle(\"c912ffc8-2360-487a-973b-59d037fb15b8\");\n```\n\nOr in its extended form:\n\n```ts\nmap.setStyle(\"https://api.maptiler.com/maps/c912ffc8-2360-487a-973b-59d037fb15b8/style.json\");\n// this could be suffixed with the API token as well\n```\n\nAnd can even be provided in the URI form:\n\n```ts\nmap.setStyle(\"maptiler://c912ffc8-2360-487a-973b-59d037fb15b8\");\n```\n\n### Globe or Mercator projection?\n\nThe **Web Mercator projection** [_(Wikipedia)_](https://en.wikipedia.org/wiki/Web_Mercator_projection) has been the go-to standard in cartography since the early days or web mapping. Partly for technical reasons but also because it is great for navigation as well as for showing the entire world in one screen, with no hidden face. That being said, Mercator's heavy distorsion at high latitudes, as well a the discontinuity at the poles can be a limitation for data visualization and has been critisized for providing a biased view of the world.\n\nThe **globe projection**, available starting from MapTiler SDK v3, does not suffer from these biases and can feel overall more playful than Mercator. It can be a great choice for semi-global data visualization, especially for data close to the poles, thanks to its geographic continuity.\n\n|               Mercator projection                |               Globe projection                |\n| :----------------------------------------------: | :-------------------------------------------: |\n| ![](images/screenshots/mercator_projection.jpeg) | ![](images/screenshots/globe_projection.jpeg) |\n\nThe choice between Mercator and Globe can be done at different levels and moments in the lifecycle of the map, yet, unless stated otherwise, **Mercator remains the default**.\n\n- In the style, using the `projection` top-level property.  \n  This projection is used when the style loads unless it has been overriden by one of the options below.\n\n  ```js\n  {\n    \"version\": ...,\n    \"id\": ...,\n    \"name\": ...,\n    \"sources\": ...,\n    \"layers\": ...,\n\n    // Make the style use the globe projection at load time\n    \"projection\": {\n      \"type\": \"globe\"\n    }\n\n    // ...\n  }\n\n  // or\n\n  {\n    \"version\": ...,\n    \"id\": ...,\n    \"name\": ...,\n    \"sources\": ...,\n    \"layers\": ...,\n\n    // Make the style use the mercator projection at load time\n    \"projection\": {\n      \"type\": \"mercator\"\n    }\n\n    // ...\n  }\n  ```\n\n- In the constructor of the `Map` class, using the `projection` option.  \n  This overrides the `projection` property from the style (if any) and will persist it if the map style changes later, even if the style contains its own projection.\n\n  ```ts\n  const map = new maptilersdk.Map({\n    container: \"map\",\n    projection: \"globe\", // Force a globe projection\n  });\n\n  // or\n\n  const map = new maptilersdk.Map({\n    container: \"map\",\n    projection: \"mercator\", // Force a mercator projection\n  });\n  ```\n\n- Using the built-in methods.  \n  This changes the projection for the current style and optionally also persists it if the map style changes later.\n\n  ```ts\n  map.setProjection(\"mercator\", { persist: true });\n  // or\n  map.setProjection(\"globe\", { persist: true });\n  ```\n\n  \u003e ℹ️ Deprecated methods `enableGlobeProjection()` and `enableMercatorProjection()` also persist the projection.\n\n  Without persistance, when the style next changes, the projection is reset to a previously persisted projection, or a projection specified in the `Map` options, or a projection specified in `projection` property of the style itself, or the default (Mercator) projection.\n  \n  ```ts\n  map.setProjection(\"mercator\");\n  // or\n  map.setProjection(\"globe\");\n  ```\n\n  The signature built in Maplibre GL JS is also usable, however a style specification other than `\"mercator\"` or `\"globe\"` can't be persisted.\n\n  ```ts\n  map.setProjection({ type: \"mercator\" });\n  // or\n  map.setProjection({ type: \"globe\" });\n  // or\n  map.setProjection({ type: [\"step\", [\"zoom\"], \"vertical-perspective\", 3, \"mercator\"] });\n  ```\n\n- Using the `MaptilerProjectionControl`. Not mounted by default, it can easily be added with a single option in the `Map` constructor:\n\n  ```ts\n  const map = new maptilersdk.Map({\n    container: \"map\",\n    projectionControl: true, // or the position such as \"top-left\", \"top-right\",\n  }); // \"bottom-right\" or \"bottom-left\"\n  ```\n\n  This dedicated control will show a globe icon \u003cimg src=\"images/screenshots/globe_icon.png\" width=\"30px\"/\u003e to transition from Mercator to globe projection and will show a flat map icon \u003cimg src=\"images/screenshots/mercator_icon.png\" width=\"30px\"/\u003e to transition from globe to Mercator projection. The chosen projection persist with future style changes.\n\n- You can also forget the persisted projection. This is the only way how to make projection specified inside style itself work again if `Map` constructor `projection` option was specified, or `map.setProjection(..., { persist: true })` was called, or `MaptilerProjectionControl` was used, starting from the next style change.\n\n  ```ts\n  map.forgetPersistedProjection();\n  ```\n\n#### Field of view (FOV)\n\nThe internal camera has a default vertical field of view [_(wikipedia)_](https://en.wikipedia.org/wiki/Field_of_view) of a wide ~36.86 degrees. In globe mode, such a large _FOV_ reduces the amount of the Earth that can be seen at once and exaggerates the central part, comparably to a fisheye lens. In many cases, a narrower _FOV_ is preferable. Here is how to update if:\n\n```ts\n// Ajust de FOV, with values from 1 to 50\nmap.setVerticalFieldOfView(10);\n```\n\n\u003e 📣 _**Note:**_ with the Mercator projection, it is possible to set a FOV of `0`, which yields a true orthographic projection [_(wikipedia)_](https://en.wikipedia.org/wiki/Orthographic_projection), but the globe projection does not allow this.\n\nHere is a table of FOV comparison:\n| 01° | 10° | 20° | 30° | 40° | 50° |\n| :--------: | :-------: |:-------: |:-------: |:-------: |:-------: |\n| ![](images/screenshots/fov_1.jpeg) | ![](images/screenshots/fov_10.jpeg) | ![](images/screenshots/fov_20.jpeg) | ![](images/screenshots/fov_30.jpeg) | ![](images/screenshots/fov_40.jpeg) | ![](images/screenshots/fov_50.jpeg) |\n\n### Globe screenshots\n\n![](images/screenshots/globe_topo_arctica.jpeg)\n![](images/screenshots/globe_hybrid.jpeg)\n![](images/screenshots/globe_outdoor_alaska.jpeg)\n![](images/screenshots/globe_outdoor_emea.jpeg)\n![](images/screenshots/globe_outdoor_panamerica.jpeg)\n\n\u003e 📣 _**Note:**_ Terrain is not fully compatible with the globe projection yet so it's better to disable it at low zoom level (from afar) and to choose the Mercator projection at higher zoom level (from up close).\n\n### Centering the map on visitors\n\nIt is sometimes handy to center the map on the visitor's location, and there are multiple ways of doing it but for the SDK, we have decided to make this extra simple by using the [IP geolocation](#%EF%B8%8F%EF%B8%8F-geolocation) API provided by [MapTiler Geolocation API](https://docs.maptiler.com/cloud/api/geolocation/), directly exposed as a single option of the `Map` constructor. There are two strategies:\n\n1. `POINT`: centering the map on the actual visitor location, optionally using the `zoom` option (zoom level `13` if none is provided). As a more precise option, if the user has previously granted access to the browser location (more precise) then this is going to be used.\n2. `COUNTRY`: fitting the map view on the bounding box of the visitor's country. In this case, the `zoom` option, if provided, will be ignored\n\nHere is how the map gets centered on the visitor's location:\n\n```js\nnew maptilersdk.Map({\n  // ... other options\n\n  geolocate: maptilersdk.GeolocationType.POINT,\n});\n```\n\nHere is how the map fits the visitor's country bounds:\n\n```js\nnew maptilersdk.Map({\n  // ... other options\n\n  geolocate: maptilersdk.GeolocationType.COUNTRY,\n});\n```\n\nThe `geolocation` options will not be taken into consideration in the following cases:\n\n- if the `center` option is provided, then it prevails\n- if the `hash` option is provided with the value `true` **AND** a location hash is already part of the URL. If `hash` is `true` but there is not yet a location hash in the URL, then the geolocation will work.\n\n\u003e 📣 _**Note:**_ if none of the options `center` or `hash` is provided to the `Map` constructor, then the map will be centered using the `POINT` strategy, unless the `geolocate` has the value `false`.\n\n\u003e 📣 _**Note 2:**_ the term _IP geolocation_ refers to finding the physical location of a computer using its _IP address_. The _IP address_ is a numerical identifier of a computer within a network, just like the phone number for a telephone. The _IP geolocation_ is **not** using the GPS of a device and usually provides a precision in the order of a few hundred meters. This precision may vary based on many local parameters such as the density of the network grid or the terrain, this is why it is generally better not to use a zoom level higher than `14`.\n\n### Easy to add controls\n\nThe term \"control\" is commonly used for all sorts of buttons and information displays that take place in one of the corners of the map area. The most well-known are probably the `[+]` and `[-]` zoom buttons as well as the attribution information. Plenty of others are possible and we have made a few easy to add and directly accessible from the `Map` constructor options:\n\n- `navigationControl`\n  - Shows the `[+]`, `[-]` and tilt/bearing/compass buttons\n  - a boolean or a corner position. Showing on the top-right by default. Hidden if `false`.\n- `geolocateControl`\n  - Shows an arrow-shaped locate button. When clicked, it adds a marker and centers the map. If clicked again, the marker disappears (unless the map was moved since first clicked)\n  - a boolean or a corner position. Showing on the top-right by default. Hidden if `false`.\n- `terrainControl`\n  - Shows a button to enable/disable the 3D terrain (does not tilt the map)\n  - a boolean or a corner position. Hidden by default, showing on top-right if `true`.\n- `scaleControl`\n  - Shows a distance scale. The unit (`\"metric\"`, `\"imperial\"` or `\"nautical\"`) can be set in the config object `config.unit` (default: `\"metric\"`)\n  - a boolean or a corner position. Hidden by default, showing on bottom-right if `true`.\n- `fullscreenControl`\n  - Shows a button that toggles the map into fullscreen\n  - a boolean or a corner position. Hidden by default, showing on top-right if `true`.\n\nThe corner positions possible are:\n\n- `\"top-left\"`\n- `\"top-right\"`\n- `\"bottom-left\"`\n- `\"bottom-right\"`\n\n**Example:**\n\n```ts\nimport { Map } from \"@maptiler/sdk\";\n\nconst map = new Map({\n  container: document.getElementById(\"my-container-div\"),\n  terrainControl: false,\n  scaleControl: true,\n  fullscreenControl: \"top-left\",\n});\n```\n\n### 🧩 Custom Controls\n\nMapTiler SDK JS supports two flexible ways to add custom controls to your map interface. Whether you're building a dynamic application or integrating with static HTML, there's a matching approach for both.\n\n#### Programmatic Controls\n\nUse `map.addControl()` with `MaptilerExternalControl` to register custom UI elements manually. This approach is ideal for dynamic logic, event-driven behavior, or integration with frameworks like React. The control element can be provided either as a reference to the **element itself**, or as its **CSS selector**. Optionally, two callback functions can be provided:\n\n- `onClick` function that is called when the element is clicked, and\n- `onRender` function that is called every time the map renders a new state.\n\nBoth callbacks receive the active `Map` instance, the associated control element itself, and an event object associated with the original event (`PointerEvent` and `MapLibreEvent` respectively).\n\n##### Example\n\n```ts\nconst panControl = new maptilersdk.MaptilerExternalControl(\n  \".pan-control\",\n  (map) =\u003e map.panBy([10, 10]), // Move southeast on click\n  (map, el) =\u003e\n    el.classList.toggle(\n      // Style based on hemisphere\n      \"northern-hemisphere\",\n      map.getCenter().lat \u003e 0,\n    ),\n);\nmap.addControl(panControl);\n\nconst element = document.createElement(\"button\");\nelement.textContent = \"Pan NW\";\nmap.addControl(\n  new maptilersdk.MaptilerExternalControl(\n    element,\n    (map) =\u003e map.panBy([-10, -10]), // Move northwest\n  ),\n  \"top-left\",\n);\n```\n\n##### Behavior Overview\n\n- On add, control element is moved into the map UI\n- `onClick` binds user interaction\n- `onRender` enables state-based styling or logic\n- Control maintains its own DOM context\n- On removal, element is returned to its original DOM position (if any) to not interfere with DOM handling of frameworks like React\n\n#### Declarative Controls\n\nAdd controls using HTML attributes – no JavaScript required. This is perfect for simple UI setups.\n\n##### Enabling Detection\n\n```ts\nconst map = new maptilersdk.Map({\n  container: \"map\",\n  customControls: true, // or \".custom-ui\"\n});\n```\n\nYou can pass `true` to enable detection globally, or a CSS selector to scope it.\n\n##### Declaring Controls\n\nUse `data-maptiler-control` attribute:\n\n```html\n\u003cbutton data-maptiler-control=\"zoom-in\"\u003e+\u003c/button\u003e\n```\n\nSupported values:\n\n| Value               | Description                                      |\n| ------------------- | ------------------------------------------------ |\n| `zoom-in`           | Zooms in                                         |\n| `zoom-out`          | Zooms out                                        |\n| `toggle-projection` | Switches Mercator ↔ Globe                       |\n| `toggle-terrain`    | Toggles terrain layer                            |\n| `reset-view`        | Resets bearing, pitch, and roll                  |\n| `reset-bearing`     | Resets bearing only                              |\n| `reset-pitch`       | Resets pitch only                                |\n| `reset-roll`        | Resets roll only                                 |\n| _(empty)_           | Registers control without built-in functionality |\n\n\u003e ⚠️ An error is thrown if an unrecognized value is used.\n\n##### Grouping Controls\n\nUse `data-maptiler-control-group` to group buttons:\n\n```html\n\u003cdiv data-maptiler-control-group\u003e\n  \u003cbutton data-maptiler-control=\"zoom-in\"\u003e+\u003c/button\u003e\n  \u003cbutton data-maptiler-control=\"zoom-out\"\u003e−\u003c/button\u003e\n\u003c/div\u003e\n```\n\nGroups are styled and positioned together but don't add functionality themselves. Functional behavior is attached to valid descendant elements.\n\n##### Positioning Controls\n\nUse `data-maptiler-position` to set placement:\n\n```html\n\u003cbutton data-maptiler-control=\"reset-view\" data-maptiler-position=\"top-left\"\u003e↻\u003c/button\u003e\n\u003cdiv data-maptiler-control-group data-maptiler-position=\"bottom-right\"\u003e\n  \u003cbutton data-maptiler-control=\"zoom-in\"\u003e+\u003c/button\u003e\n  \u003cbutton data-maptiler-control=\"zoom-out\"\u003e−\u003c/button\u003e\n\u003c/div\u003e\n```\n\nValid positions: `'top-left'`, `'top-right'`, `'bottom-left'`, `'bottom-right'`\n\n##### Styling with CSS Variables\n\nWhen declarative controls are enabled, the map container exposes dynamic CSS variables:\n\n| Variable                         | Description                                      | Type            |\n| -------------------------------- | ------------------------------------------------ | --------------- |\n| `--maptiler-center-lng`          | Longitude of center                              | unitless number |\n| `--maptiler-center-lat`          | Latitude of center                               | unitless number |\n| `--maptiler-zoom`                | Zoom level                                       | unitless number |\n| `--maptiler-bearing`             | Map rotation                                     | unitless number |\n| `--maptiler-pitch`               | Pitch angle                                      | unitless number |\n| `--maptiler-roll`                | Roll angle                                       | unitless number |\n| `--maptiler-is-globe-projection` | `true` if globe is active\u003cbr\u003e`false` otherwise   | string          |\n| `--maptiler-has-terrain`         | `true` if terrain is active\u003cbr\u003e`false` otherwise | string          |\n\n##### Example\n\n```css\n.compass-icon {\n  transform: rotateX(calc(var(--maptiler-pitch) * 1deg)) rotateZ(calc(var(--maptiler-bearing) * -1deg));\n}\n\n@container style(--maptiler-is-globe-projection: true) {\n  .projection-icon {\n    content: \"globe\";\n  }\n}\n```\n\n### 3D terrain in one call\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/screenshots/grandcanyon.gif\" width=\"48%\"\u003e\u003c/img\u003e\n  \u003cimg src=\"images/screenshots/alps.gif\" width=\"48%\"\u003e\u003c/img\u003e\n\u003c/p\u003e\n\nDo you want to enable 3D terrain? That's easy now with a single function call:\n\n```ts\n// With the default exaggeration factor of 1\nmap.enableTerrain();\n\n// Or, if you want to boost some volume a little\nmap.enableTerrain(1.5);\n```\n\nThe terrain can also be enabled directly from the `Map` constructor, with the options `terrain` (a boolean, `false` by default) and `terrainExaggeration` (a number, `1` by default):\n\n```ts\nconst map = new Map({\n  // some options...\n  terrain: true,\n  terrainExaggeration: 1.5,\n});\n```\n\nAt any point, you can modify the exaggeration factor:\n\n```ts\nmap.setTerrainExaggeration(2);\n```\n\nOr simply disable it:\n\n```ts\nmap.disableTerrain();\n```\n\n\u003e 📣 _**Note:**_ Keep in mind that setting an exaggeration factor at `0` will result in the same result as disabling the elevation but that terrain RGB tiles will still be fetched in the background.\n\n\u003e 📣 _**Note 2:**_ please be aware that due to the volume and elevation of the map floor in 3D space, the navigation with the terrain enabled is slightly different than without.\n\nBy default, enabling, disabling or even just updating the terrain exaggeration will result in a 1-second animation. This is possible to modify with the following `Map` method:\n\n```ts\n// Duration in milliseconds\nmap.setTerrainAnimationDuration(500);\n```\n\n#### Terrain events\n\n- `\"terrain\"` event\n\nAs an extension of Maplibre GL JS, MapTiler SDK is also exposing the terrain event `\"terrain\"`. This event is triggered when a terrain source is added or removed:\n\n```ts\nmap.on(\"terrain\", (e) =\u003e {\n  // your logic here\n});\n```\n\nSince MapTiler SDK adds animation and the terrain data is necessary all along, the `\"terrain\"` event will be called at the very beginning of the terrain animation when enabling and at the very end when disabling.\n\n- `\"terrainAnimationStart\"` and `\"terrainAnimationStop\"` events\n\nWith the animation of the terrain, it can sometimes be convenient to know when the animation starts and ends. These two events are made just for that, here are how they work:\n\n```ts\nmap.on(\"terrainAnimationStart\", (event) =\u003e {\n  console.log(\"Terrain animation is starting...\");\n});\n\nmap.on(\"terrainAnimationStop\", (event) =\u003e {\n  console.log(\"Terrain animation is finished\");\n});\n```\n\nThe `event` argument is an object that contains (among other things) a `terrain` attribute. In the case of `\"terrainAnimationStop\"`, this terrain attribute is `null` if the animation was about disabling the terrain, otherwise, this is just a propagation of `map.terrain`.\n\nIn the following example, we decide to associate the terrain animation with a change of camera, e.g. from clicking on the terrain control:\n\n- when the terrain is enabled, it pops up with an animation and only **then** the camera is animated to take a lower point of view\n- when the terrain is disabled, it is flattened with an animation and only **then** the camera is animated to a top view\n\n```ts\nmap.on(\"terrainAnimationStop\", (e) =\u003e {\n  map.easeTo({\n    pitch: e.terrain ? 60 : 0,\n    duration: 500,\n  });\n});\n```\n\n### Halo and Space Options\n\nThe `halo` and `space` options allow for enhanced visual customization of the map, especially for globe projections.\n\n#### `halo` (Atmospheric Glow)\n\nThe `halo` option adds a gradient-based atmospheric glow around the globe, simulating the visual effect of Earth's atmosphere when viewed from space.\n\n##### Usage:\n\nYou can enable a simple halo by setting it to `true`:\n\n```ts\nconst map = new maptilersdk.Map({\n  container: document.getElementById(\"map\"),\n  style: maptilersdk.MapStyle.OUTDOOR,\n  halo: true,\n});\n```\n\nFor more customization, you can define a radial gradient with scale and stops:\n\n```ts\nconst map = new maptilersdk.Map({\n  container: document.getElementById(\"map\"),\n  style: maptilersdk.MapStyle.OUTDOOR,\n  halo: {\n    scale: 1.5, // Controls the halo size\n    stops: [\n      [0.2, \"transparent\"],\n      [0.2, \"red\"],\n      [0.4, \"red\"],\n      [0.4, \"transparent\"],\n      [0.6, \"transparent\"],\n      [0.6, \"red\"],\n      [0.8, \"red\"],\n      [0.8, \"transparent\"],\n      [1.0, \"transparent\"],\n    ],\n  },\n});\n```\n\nYou can also set the halo dynamically after the map loads:\n\n```ts\nmap.on(\"load\", () =\u003e {\n  map.setHalo({\n    scale: 2,\n    stops: [\n      [0.0, \"rgba(135,206,250,1)\"],\n      [0.5, \"rgba(0,0,250,0.75)\"],\n      [0.75, \"rgba(250,0,0,0.0)\"],\n    ],\n  });\n});\n```\n\nTo disable state transitions for halo or space:\n\n```ts\nmap.disableHaloAnimations();\nmap.disableSpaceAnimations();\n```\n\n#### `space` (Background Environment)\n\nThe space option allows customizing the background environment of the globe, simulating deep space or skybox effects.\n\n##### Usage\n\nYou can enable a simple space background with a solid color:\n\n```ts\nconst map = new maptilersdk.Map({\n  container: document.getElementById(\"map\"),\n  style: maptilersdk.MapStyle.OUTDOOR,\n  space: {\n    color: \"#111122\", // Dark space-like color\n  },\n});\n```\n\nAlternatively, you can provide a cubemap for a space backround using one of the following methods:\n\n###### Predefined Presets:\n\n- `space`: Dark blue hsl(210, 100%, 4%) background and white stars (transparent background image). Space color changes the background color, stars always stay white.\n- `stars` (default): Black background (image mask), space color changes the stars color, background always stays black.\n- `milkyway`: Black half-transparent background with standard milkyway and stars. Space color changes the stars and milkyway color, background always stays black.\n- `milkyway-subtle`: Black half-transparent background with subtle milkyway and less stars. Space color changes the stars and milkyway color, background always stays black.Black half-transparent background with standard milkyway and stars. Space color changes the stars and milkyway color, background always stays black.\n- `milkyway-bright`: Black half-transparent background with bright milkyway and more stars. Space color changes the stars and milkyway color, background always stays black.\n- `milkyway-colored`: Full background image with natural space colors. Space color doesn’t change anything (non transparent image).\n\n```ts\nconst map = new maptilersdk.Map({\n  container: document.getElementById(\"map\"),\n  style: maptilersdk.MapStyle.OUTDOOR,\n  space: {\n    preset: \"space\",\n  },\n});\n```\n\n###### Cubemap Images (Custom Skybox):\n\n```ts\nconst map = new maptilersdk.Map({\n  container: document.getElementById(\"map\"),\n  style: maptilersdk.MapStyle.OUTDOOR,\n  space: {\n    faces: {\n      nX: \"/path-to-image/nX.png\",\n      nY: \"/path-to-image/nY.png\",\n      nZ: \"/path-to-image/nZ.png\",\n      pX: \"/path-to-image/pX.png\",\n      pY: \"/path-to-image/pY.png\",\n      pZ: \"/path-to-image/pZ.png\",\n    },\n  },\n});\n```\n\n###### Cubemap Path with image format\n\nThis fetches all images from a path, this assumes all files are named px, nx, py, ny, pz, nz and suffixed with the appropriate extension specified in `format`.\n\n```ts\nconst map = new maptilersdk.Map({\n  container: document.getElementById(\"map\"),\n  style: maptilersdk.MapStyle.OUTDOOR,\n  space: {\n    path: {\n      baseUrl: \"spacebox/transparent\",\n      format: \"png\", // Defaults to PNG\n    },\n  },\n});\n```\n\n###### Set the space background dynamically:\n\n```ts\nmap.on(\"load\", () =\u003e {\n  map.setSpace({\n    color: \"red\",\n    path: {\n      baseUrl: \"spacebox/transparent\",\n    },\n  });\n});\n```\n\nNote: if `space.color` or `space.\u003cfaces | path | preset\u003e` are not explicitly set in the call to `setSpace`, then the previous value will remain for this field.\n\nFurther code examples can be found in `~/demos/`\n\n### `ImageViewer`\n\nMapTiler's `ImageViewer` component allows you to display tiled, non-georeferenced images but interact with them in almost the same way you would if you were displaying map. These can be handy for zoomable non-georeferenced, geographically \"inaccurate\" maps such as hotel maps, golf courses, theme parks etc. Think pixels instead of lattitudes and longtidues.\n\n```ts\n  export type ImageViewerConstructorOptions = {\n    imageUUID: string; // the unique UUID of the image object you are displaying\n    center?: [number, number]; // the center you want the viewer to init on, defaults to the center of the image.\n    container: string | HTMLElement // the container element you want to mount the viewer on\n    apiKey: string; // your MapTiler API Key\n    zoom?: number;\n    maxZoom?: number;\n    minZoom?: number;\n    bearing?: number;\n    debug?: boolean; // whether you want to debug the tiles\n  };\n\n  const imageViewer = new ImageViewer({\n    container: document.getElementById(\"map\")!, // the container element you want to use\n    apiKey: \"YOUR_MAPTILER_API_KEY\", // your api key\n    imageUUID: \"11111111-2222-3333-4444-555555555555\", // unique UUID of the image object\n    // ...other options, see below\n  }: ImageViewerConstructorOptions);\n\n  await imageViewer.onReadyAsync()\n\n  // OR\n\n  imageViewer.on(\"imageviewerready\", () =\u003e { console.log('Ready!') })\n```\n\n#### Methods\n\n`ImageViewer` provides a subset of methods for interaction with the map. A major caveat is that the `ImageViewer` component works in pixels and not in LngLat. Thus, when using methods such as `setCenter` or `flyTo` the pixel values provided refer to the _absolute pixel position_ on the image, not screen pixel position.\n\nImagine your image is 10,000px x 10,000px, if regardless if your zoom is 2 or 4, calling `.setCenter(500,500)` will always position the viewer over the same part of the image.\n\nFor full details on supported methods see the type declaration for `ImageViewer` or visit the (docs)[https://docs.maptiler.com/sdk-js/examples/image-viewer/].\n\n#### Events\n\nIn a similar manner, a subset of map events are fired by the image viewer. All UI interaction events that would normally include a `LngLat` in the event data instead receive an `imageX` and `imageY` field, representing an absolute pixel position of the image. This is same for `flyTo`, `jumpTo`, `panTo` etc.\n\nA full list of supported events can be found in the exported type declaration `ImageViewerEventTypes`\n\n#### Markers with `ImageViewerMarker`\n\nAn `ImageViewerMarker` can also be added and used like the usual `Marker`class, the main difference being it operates in _image pixels_, not LngLat coordinates.\n\n```ts\nconst marker = new ImageViewerMarker({ draggable: true });\n\nmarker\n  .setPosition([100, 100]) // position in image pixels.\n  .addTo(imageViewer)\n  .on(\"dragend\", (e) =\u003e {\n    console.log(\"e.target.isWithinImageBounds()\", e.target.isWithinImageBounds());\n  });\n```\n\nFull API documentation can be found in the typedocs: `npm run doc`\n\n### Easy language switching\n\nThe language generally depends on the style but we made it possible to easily set and update from a built-in list of languages.\n\nThe built-in list of supported languages is accessible from the `Language` object:\n\n```ts\nimport { Language } from \"@maptiler/sdk\";\n```\n\nIn the UMD bundle, it will be directly at `maptilersdk.Language`.\n\nThere are three distinct ways to set the language of a map:\n\n1. **Global way, using the config object:**\n\n```ts\nimport { config } from \"@maptiler/sdk\";\n\nconfig.primaryLanguage = Language.ENGLISH;\n```\n\nThen, if any further language setting is applied, all the map instances created afterward will use this language.\n\n2. **Set the language at instantiation time:**\n\n```ts\nconst map = new Map({\n  // some options...\n  language: Language.ENGLISH, // the ISO codes can also be used (eg. \"en\")\n});\n```\n\nIt will only apply `ENGLISH` as the language of this specific map instance (and will not alter the global `config`).\n\n3. **Set the language after the map has been instantiated:**\n\n```ts\nmap.setLanguage(Language.ENGLISH);\n```\n\nAgain, it will only apply `ENGLISH` as the language of this specific map instance (and will not alter the global `config`).\n\nThe list of supported languages is built-in and can be found [here](src/language.ts). In addition, there are special language _flags_:\n\n- `Language.AUTO` **[DEFAULT]** uses the language defined in the web browser\n- `Language.STYLE_LOCK` to strictly use the language defined in the style. Prevents any further language update\n- `Language.LOCAL` uses the language local to each country\n- `Language.LATIN` uses a default with Latin characters\n- `Language.NON_LATIN` uses a default with non-Latin characters\n\nWhenever a label is not supported in the defined language, it falls back to `Language.LOCAL`.\n\nHere is a sample of some compatible languages:\n![](images/screenshots/multilang.gif)\n\n#### Built-in support for right-to-left languages\n\nLanguages that are written right-to-left such as Arabic and Hebrew are fully supported by default. No need to install any plugins!\n\nIf you wish to opt of applying the rtl plugin or wish to use a different compatible rtl text plugin, you can pass the `rtlTextPlugin`\nconstructor option as either `false` (disable the rtl plugin) or a url to load a different plugin.\n\n**Note: Once the rtlTextPlugin has been installed once, it cannot be unset nor updated on the current instance. Calling `setRTLTextPlugin` without setting `rtlTextPlugin` to false in the constuctor will result in an error.**\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/screenshots/lang-arabic.jpeg\" width=\"48%\"\u003e\u003c/img\u003e\n  \u003cimg src=\"images/screenshots/lang-hebrew.jpeg\" width=\"48%\"\u003e\u003c/img\u003e\n\u003c/p\u003e\n\n#### Visitor language modes\n\nThe _visitor_ language modes are special built-in modes made to display labels in two different languages, concatenated when available:\n\n- `Language.VISITOR` concatenates labels in the language of your system and the _local_ language\n- `Language.VISITOR_ENGLISH` concatenates labels in English and the _local_ language\n\n```ts\nconst map = new Map({\n  // some options...\n  language: Language.VISITOR,\n});\n\n// or\n\nconst map = new Map({\n  // some options...\n  language: Language.VISITOR_ENGLISH,\n});\n```\n\nWe believe these two modes can be very handy to help the end users identify places, especially when the local labels are not using a latin charset. Here is how it looks like:\n\n![](images/screenshots/visitor_athen.png)\n![](images/screenshots/visitor_osaka.png)\n\n### Custom Events and Map Lifecycle\n\n#### Events\n\n##### The `ready` event\n\nThe `ready` event happens just after the `load` event but waits until all the controls managed by the `Map` constructor are dealt with, some having an asynchronous logic to set up.  \nSince the `ready` event waits until all the basic controls are nicely positioned, it is **safer** to use `ready` than `load` if you plan to add other custom controls with the `.addControl()` method.\n\nThis event works exactly the same way as `load` and you can safely replace those by `\"ready\"`. Here is a usage example:\n\n```js\nconst map = new maptilersdk.Map({\n  container: \"map-container\",\n});\n\nmap.on(\"ready\", (evt) =\u003e {\n  const terrainControl = new maptilersdk.MaptilerTerrainControl();\n  map.addControl(terrainControl);\n});\n```\n\n##### The `loadWithTerrain` event\n\nThe `loadWithTerrain` event is triggered only _once_ in a `Map` instance lifecycle, when both the `ready` event and the `terrain` event **with non-null terrain** are fired.\n\n**Why a new event?**\nWhen a map is instantiated with the option `terrain: true`, then MapTiler terrain is directly added to it and some animation functions such as `.flyTo()` or `.easeTo()` if started straight after the map initialization will actually need to wait a few milliseconds that the terrain is properly initialized before running.  \nRelying on the `ready` or `load` event to run an animation with a map with terrain may fail in some cases for this reason, and this is why waiting for `loadWithTerrain` is safer in this particular situation.\n\n#### Lifecycle Methods\n\nThe events `load`, `ready` and `loadWithTerrain` are both called _at most once_ and require a callback function to add more elements such as markers, layers, popups and data sources. Even though MapTiler SDK fully supports this logic, we have also included a _promise_ logic to provide a more linear and less nested way to wait for a Map instance to be usable. Let's compare the two ways:\n\n- Classic: with a callback on the `load` event:\n\n```ts\nfunction init() {\n  const map = new Map({\n    container,\n    center: [2.34804, 48.85439], // Paris, France\n    zoom: 14,\n  });\n\n  // We wait for the event.\n  // Once triggered, the callback is ran in its own scope.\n  map.on(\"load\", (evt) =\u003e {\n    // Adding a data source\n    map.addSource(\"my-gps-track-source\", {\n      type: \"geojson\",\n      data: \"https://example.com/some-gps-track.geojson\",\n    });\n  });\n}\n```\n\n- Modern: with a promise returned by the method `.onLoadAsync()`, used in an `async` function:\n\n```ts\nasync function init() {\n  const map = new Map({\n    container,\n    center: [2.34804, 48.85439], // Paris, France\n    zoom: 14,\n  });\n\n  // We wait for the promise to resolve.\n  // Once triggered, the rest of the init function runs\n  await map.onLoadAsync();\n\n  // Adding a data source\n  map.addSource(\"my-gps-track-source\", {\n    type: \"geojson\",\n    data: \"https://example.com/some-gps-track.geojson\",\n  });\n}\n```\n\nWe deployed exactly the same logic for the `loadWithTerrain` event. Let's see how the two ways compare.\n\n- Classic: with a callback on the `loadWithTerrain` event:\n\n```ts\nfunction init() {\n  const map = new Map({\n    container,\n    center: [2.34804, 48.85439], // Paris, France\n    zoom: 14,\n    terrain: true,\n  });\n\n  // We wait for the event.\n  // Once triggered, the callback is ran in its own scope.\n  map.on(\"loadWithTerrain\", (evt) =\u003e {\n    // make an animation\n    map.flyTo({\n      center: [-0.09956, 51.50509], // London, UK\n      zoom: 12.5,\n    });\n  });\n}\n```\n\n- Modern: with a promise returned by the method `.onLoadWithTerrainAsync()`, used in an `async` function:\n\n```ts\nasync function init() {\n  const map = new Map({\n    container,\n    center: [2.34804, 48.85439], // Paris, France\n    zoom: 14,\n    terrain: true,\n  });\n\n  // We wait for the promise to resolve.\n  // Once triggered, the rest of the init function runs\n  await map.onLoadWithTerrainAsync();\n\n  // make an animation\n  map.flyTo({\n    center: [-0.09956, 51.50509], // London, UK\n    zoom: 12.5,\n  });\n}\n```\n\nAnd finally, the lifecycle method corresponding to the `ready` event:\n\n- Classic: with a callback on the `ready` event:\n\n```ts\nfunction init() {\n  const map = new Map({\n    container,\n    center: [2.34804, 48.85439], // Paris, France\n    zoom: 14,\n  });\n\n  // We wait for the event.\n  // Once triggered, the callback is ran in its own scope.\n  map.on(\"ready\", (evt) =\u003e {\n    // Adding a data source\n    map.addSource(\"my-gps-track-source\", {\n      type: \"geojson\",\n      data: \"https://example.com/some-gps-track.geojson\",\n    });\n  });\n}\n```\n\n- Modern: with a promise returned by the method `.onReadyAsync()`, used in an `async` function:\n\n```ts\nasync function init() {\n  const map = new Map({\n    container,\n    center: [2.34804, 48.85439], // Paris, France\n    zoom: 14,\n  });\n\n  // We wait for the promise to resolve.\n  // Once triggered, the rest of the init function runs\n  await map.onReadyAsync();\n\n  // Adding a data source\n  map.addSource(\"my-gps-track-source\", {\n    type: \"geojson\",\n    data: \"https://example.com/some-gps-track.geojson\",\n  });\n}\n```\n\nWe believe that the _promise_ approach is better because it does not nest scopes and will allow for a linear non-nested stream of execution. It also corresponds to more modern development standards.\n\n\u003e 📣 _**Note:**_ Generally speaking, _promises_ are not a go to replacement for all event+callback and are suitable only for events that are called only once in the lifecycle of a Map instance. This is the reason why we have decided to provide a _promise_ equivalent only for the `load`, `ready` and `loadWithTerrain` events but not for events that may be called multiple time such as interaction events.\n\n##### The `webglContextLost` event\n\nThe map is rendered with WebGL, that leverages the GPU to provide high-performance graphics. In some cases, the host machine, operating system or the graphics driver, can decide that continuing to run such high performance graphics is unsustainable, and will abort the process. This is called a \"WebGL context loss\". Such situation happens when the resources are running low or when multiple browser tabs are competing to access graphics memory.\n\nThe best course of action in such situation varies from an app to another. Sometimes a page refresh is the best thing to do, in other cases, instantiating a new Map dynamically at application level is more appropriate because it hides a technical failure to the end user. The event `webglContextLost` is exposed so that the most appropriate scenario can be implemented at application level.\n\nHere is how to respond to a WebGL context loss with a simple page refresh:\n\n```ts\n// Init the map\nconst map = new maptilersdk.Map({\n  container: \"map-container\",\n  hash: true,\n});\n\n// Refresh the page if context is lost.\n// Since `hash` is true, the location will be the same as before\nmap.on(\"webglContextLost\", (e) =\u003e {\n  location.reload();\n});\n```\n\n### Color Ramps\n\nA color ramp is a color gradient defined in a specific interval, for instance in [0, 1], and for any value within this interval will retrieve a color. They are defined by at least a color at each bound and usually additional colors within the range.\n\nColor ramps are super useful to represent numerical data in a visual way: the temperature, the population density, the average commute time, etc.\n\nThe SDK includes many built-in ready-to-use color ramps as well as extra logic to manipulate them and create new ones, here is the full list:\n\n![](images/colorramps.png)\n\nTo use an already existing color ramp and access some of its values:\n\n```ts\nimport { ColorRampCollection } from \"@maptiler/sdk\";\n\n// The TURBO color ramp, just like all the built-ins, is defined in [0, 1],\n// but we can rescale it to fit the range of temperature [-18, 38]°C (equivalent to [0, 100]F)\n// and this actually creates a clone of the original TURBO\nconst temperatureTurbo = ColorRampCollection.TURBO.scale(-18, 38);\n\n// What's the color at 0°C (or 32F) ?\nconst zeroColor = temperatureTurbo.getColor(0);\n// The color is an array: [45, 218, 189, 255]\n\n// Alternatively, we can ask for the hex color:\nconst zeroColorHex = temperatureTurbo.getColorHex(0);\n// The color is a string: \"#2ddabdff\"\n```\n\nCreating a new one consists of defining all the colors for each _color stops_. The values can be in the range of interest and _do not_ have to be in [0, 1]. For example, let's recreate a _Viridis_ color ramp but with a range going from 0 to 100:\n\n```ts\nimport { ColorRamp } from \"@maptiler/sdk\";\n\nconst myCustomRamp = new ColorRamp({\n  stops: [\n    { value: 0, color: [68, 1, 84] },\n    { value: 13, color: [71, 44, 122] },\n    { value: 25, color: [59, 81, 139] },\n    { value: 38, color: [44, 113, 142] },\n    { value: 5, color: [33, 144, 141] },\n    { value: 63, color: [39, 173, 129] },\n    { value: 75, color: [92, 200, 99] },\n    { value: 88, color: [170, 220, 50] },\n    { value: 100, color: [253, 231, 37] },\n  ],\n});\n```\n\nWhen defining a new _ramp_, the colors can be an RGB array (`[number, number, number]`) or an RGBA array (`[number, number, number, number]`).\n\nMany methods are available on color ramps, such as getting the `\u003ccanvas\u003e` element of it, rescaling it, flipping it or [resampling it in a non-linear way](colorramp.md). Read more on [our reference page](https://docs.maptiler.com/sdk-js/api/color-ramp/) and have a look at our [examples](https://docs.maptiler.com/sdk-js/examples/?q=colorramp) to see how they work.\n\n### Camera routes and animations\n\nThe SDK comes with several classes to help with animations, particularly route animations.\n\nSee `demos/11-animated-routes.html` for examples.\nSee `demos/12-maptiler-animation.html` for examples.\n\n#### 🧩 `MaptilerAnimation`\n\nMaptilerAnimation is a utility class for smoothly animating between keyframes using custom easing and playback control. It supports event-based hooks for frame updates and completion, and works well within rendering loops or UI transitions.\n\n##### 🚀 Usage\n\n```ts\n// linearly animated between values\nconst animation = new MaptilerAnimation({\n  keyframes: [\n    // `props` are interpolated across the duration\n    { delta: 0, props: { lon: -7.445, } },\n    // `userData` can hold any type of custom data to pass with the keyframe\n    { delta: 0.5, userData: { mydata: \"whoa!\" } },\n    { delta: 1, props: { lon: -7.473 } }\n  ],\n  duration: 1000, // 1 second\n  iterations: Infinity // loop forever\n});\n\nconst marker = new Marker().setLngLat(\n  new LngLat(\n    -7.449346225791231,\n    39.399728941536836,\n  )\n).addTo(map);\n\n// TimeUpdate is fired every frame\nanimation.addEventListener(AnimationEventTypes.TimeUpdate, (e) =\u003e {\n  marker.setLngLat(\n    new LngLat(\n      e.props.lon,\n    39.399728941536836,\n    )\n  )\n})\n// fired when the keyframe changes\nanimation.addEventListener(AnimationEventTypes.Keyframe, ({ userData }) =\u003e {\n  console.log(userData.mydata) // \"whoa!\"\n});\n\nanimation.play();\n```\n![](images/animate-linear-trimmed.gif)\n\n```ts\n// eased between values\nconst animation = new MaptilerAnimation({\n  keyframes: [\n    // `props` are interpolated across the duration\n    { delta: 0, easing: EasingFunctionName.ElasticInOut, props: { lon: -7.445, } },\n    { delta: 1, props: { lon: -7.455 } }\n  ],\n  duration: 1000, // 1 second\n  iterations: Infinity // loop forever\n});\n```\n![](images/animate-elastic-trimmed.gif)\n\n####  🗺️ `AnimatedRouteLayer`\n\n`AnimatedRouteLayer` is custom layer that animates a path or route on the map based on keyframes or GeoJSON data. It supports animated line styling and camera following, making it ideal for visualizing routes, playback tracks, or timeline-based geographic events.\n\nNote: At present, to avoid problems arising from the camera being manipulated by two animations at any one time, there can only ever be one instance of `AnimatedRouteLayer` on the map at any time. This API may change in the future, but at present you must remove each instance of `AnimatedRouteLayer` from the map before adding another.\n\n##### ✨ Features\n  - Animate a path using keyframes or GeoJSON data\n  - Optional animated stroke styles to indicate progress\n  - Camera movement smoothing, following along the route\n  - Configurable duration, easing, delay, and iterations via geojson properties\n  - Event-based lifecycle hooks for adaptability.\n  - Optional manual frame advancement (e.g., for scrubbing or syncing with map events, scroll etc etc)\n\n##### 🚀 Basic Usage\n```ts\nconst myGeoJSONSource = {\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"geometry\": {\n        \"type\": \"LineString\",\n        \"coordinates\": [\n          [-74.0060, 40.7128],\n          [-73.9352, 40.7306],\n          [-73.9851, 40.7580]\n        ]\n      },\n      \"properties\": {\n        \"@duration\": 5000, // animation params are prepended with '@'\n        \"@iterations\": 3,\n        \"@delay\": 1000,\n        \"@autoplay\": true,\n        \"bearing\": [\n          40,\n          30,\n          10,\n          10,\n          20,\n          40,\n        ]\n      }\n    }\n  ]\n}\n\nmap.addSource(\"my-geojson-source\", {\n  type: \"geojson\",\n  data: myGeoJSONSource,\n});\n\nconst animatedRoute = new AnimatedRouteLayer({\n  source: {\n    // assumes that the source is already added to the map with the given layer ID\n    id: \"my-geojson-source\", // the name of the source\n    layerID: \"route-layer\", // the name of the layer\n  },\n  // OR\n  keyframes: [], // an array of keyframes\n\n  duration: 5000,\n  pathStrokeAnimation: {\n    // will only be applied to LineString GeoJSON types\n    activeColor: [0, 128, 0, 1], // color of the line that has already been traversed\n    inactiveColor: [128, 128, 128, 0.5],\n  },\n  cameraAnimation: {\n    follow: true, // should the camera follow the route?\n    pathSmoothing: {\n      resolution: 20, // the resolution of the smoothness \n      epsilon: 10, // how much the path is simplified before smoothing\n    },\n  },\n  autoplay: true,\n});\n\n// Add to map\nmap.addLayer(animatedRoute);\n\n// Playback controls\nanimatedRoute.play();\nanimatedRoute.pause();\n```\n\nFor a full example of how to use this, look at [the example](./demos/11-animated-routes.html)\n\n### Vector Layer Helpers\n\n**Let's make vector layers easy!** Originally, you'd have to add a source and then proceed to the styling of your layer, which can be tricky because there are a lot of `paint` and `layout` options and they vary a lot from one type of layer to another. **But we have helpers for this!** 🖋️\n![](images/screenshots/point-layer.jpg)\n\n#### Shared logic\n\nHelpers come with a lot of **built-in defaults** and some fail-proof logic that makes creating vector layers much easier! As a result, a dataset can be displayed in one call, creating both the datasource and the layer(s) in one go!\n\nDepending on the type of feature to add (point, polyline, polygon or heatmap), a different helper function needs to be used, but datasource could contain mixed types of feature and the helper will only display a specific type.\n\nAll the helpers are made available under the `helpers` object. If you are using ES Modules, this is how you access them:\n\n```ts\nimport { Map, helpers } from \"@maptiler/sdk\";\n```\n\nIf you are using the UMD bundle of the SDK, for example from our CDN, you will find the `helpers` with:\n\n```js\nmaptilersdk.helpers;\n```\n\n**Example:** we have a _geoJSON_ file that contains both _polygons_ and _point_ and we use it as the `data` property on the `helpers.addPoint(map, { options })`, this will only add the _points_.\n\nIn addition to easy styling, the helpers' datasource can be:\n\n- a URL to a geoJSON file or its string content\n- a URL to a GPX or KML file (only for the polyline helper) or its string content\n- a UUID of a MapTiler dataset\n\n##### Multiple Layers\n\nThe key design principle of these vector layer helpers is **it's easy to make what you want**, which is very different from **making MapLibre easier to use**.\n\n\u003e For example, to create a road with an outline, one must draw two layers: a wider base layer and a narrower top layer, fueled by the same polyline data. This requires ordering the layers properly and computing not the width of the outline, but rather the width of the polyline underneath so that it outgrows the top road layer of the desired number of pixels.\n\nWith the polyline helper, you just say if you want an outline and specify its size (or even a zoom-dependent size) and everything is handled for you. As a result, calling the method `helpers.addPolyline` will return an object with **multiple IDs**: the ID of the top/main layer, the ID of the outline layer (could be `null`) and the ID of the data source. This makes further layer and source manipulation possible.\n\n##### Input\n\nThe vector layer helper also shares some _I/O_ logic: each of them can take many options but a subset of them is common across all the helpers:\n\n```ts\n/**\n * A geojson Feature collection or a URL to a geojson or the UUID of a MapTiler dataset.\n */\ndata: FeatureCollection | string;\n\n/**\n * ID to give to the layer.\n * If not provided, an auto-generated ID of the for \"maptiler-layer-xxxxxx\" will be auto-generated,\n * with \"xxxxxx\" being a random string.\n */\nlayerId?: string;\n\n/**\n * ID to give to the geojson source.\n * If not provided, an auto-generated ID of the for \"maptiler-source-xxxxxx\" will be auto-generated,\n * with \"xxxxxx\" being a random string.\n */\nsourceId?: string;\n\n/**\n * The ID of an existing layer to insert the new layer before, resulting in the new layer appearing\n * visually beneath the existing layer. If this argument is not specified, the layer will be appended\n * to the end of the layers array and appear visually above all other layers.\n */\nbeforeId?: string;\n\n/**\n * Zoom level at which it starts to show.\n * Default: `0`\n */\nminzoom?: number;\n\n/**\n * Zoom level after which it no longer show.\n * Default: `22`\n */\nmaxzoom?: number;\n```\n\n#### Polyline Layer Helper\n\nThe method `helpers.addPolyline` is not only compatible with the traditional GeoJSON source but also with **GPX** and **KML** files and the `.data` options can be a MapTiler dataset UUID and will be resolved automatically.\n\nhere is the minimal usage, with the default line width and a random color (within a selected list):\n\n```ts\nhelpers.addPolyline(map, {\n  // A URL, relative or absolute\n  data: \"some-trace.geojson\",\n});\n```\n\n![](images/screenshots/default-trace.jpg)\n\nWe can add many options, such as specific color, a custom width or a dash pattern, this time sourcing the data from MapTiler, using the UUID of a dataset:\n\n```ts\nhelpers.addPolyline(map, {\n  data: \"74003ba7-215a-4b7e-8e26-5bbe3aa70b05\",\n  lineColor: \"#FF6666\",\n  lineWidth: 4,\n  lineDashArray: \"____ _ \",\n  lineCap: \"butt\",\n});\n```\n\n![](images/screenshots/custom-trace.jpg)\nAs you can see, we've come up with a fun and easy way to create **dash arrays**, just use _underscores_ and _white spaces_ and this pattern will repeat!\n\nAdding an outline is also pretty straightforward:\n\n```ts\nhelpers.addPolyline(map, {\n  data: \"74003ba7-215a-4b7e-8e26-5bbe3aa70b05\",\n  lineColor: \"#880000\",\n  outline: true,\n});\n```\n\n![](images/screenshots/polyline-outline.png)\n\nEndless possibilities, what about a glowing wire?\n\n```ts\nhelpers.addPolyline(map, {\n  data: \"74003ba7-215a-4b7e-8e26-5bbe3aa70b05\",\n  lineColor: \"#fff\",\n  lineWidth: 1,\n  outline: true,\n  outlineColor: \"#ca57ff\",\n  outlineWidth: 10,\n  outlineBlur: 10,\n  outlineOpacity: 0.5,\n});\n```\n\n![](images/screenshots/polyline-glow.png)\n\nAll the other options are documented on [our reference page](https://docs.maptiler.com/sdk-js/api/helpers/#polyline) and more examples are available [here](https://docs.maptiler.com/sdk-js/examples/?q=polyline+helper).\n\n#### Polygon Layer Helper\n\nThe polygon helper makes it easy to create vector layers that contain polygons, whether they are *multi*polygons, *holed*polygons or just simple polygons. Whenever it's possible and it makes sense, we use the same terminology across the different helpers.\n\nHere is a minimalist example, with a half-transparent polygon of Switzerland, from a local file:\n\n```ts\nhelpers.addPolygon(map, {\n  data: \"switzerland.geojson\",\n  fillOpacity: 0.5,\n});\n```\n\nAgain, if no color is specified, a random one from a list is being picked:\n![](images/screenshots/polygon-transparent.png)\n\nPlenty of options are available to create interesting thematic visualizations:\n\n```ts\nhelpers.addPolygon(map, {\n  data: \"switzerland.geojson\",\n  pattern: \"cheese512.png\",\n  outline: true,\n  outlineWidth: 3,\n  outlineColor: \"white\",\n  outlineDashArray: \"_ \",\n  fillOpacity: 0.7,\n});\n```\n\n![](images/screenshots/swiss-cheese.png)\n\nAll the other options are documented on [our reference page](https://docs.maptiler.com/sdk-js/api/helpers/#polygon) and more examples are available [here](https://docs.maptiler.com/sdk-js/examples/?q=polygon+helper).\n\n#### Point Layer Helper\n\nA point visualization may appear like the simplest of all, but we noticed this is where people get the most creative: cluster, data-driven variable radius, but also scaled with zoom, with or without labels, data-driven colors, etc. Our helper supports all of these and will fill-in with built-in default for what's missing.\n\nHere is the simplest example, with a dataset loaded from a local file:\n\n```ts\nhelpers.addPoint(map, {\n  data: \"public-schools.geojson\",\n});\n```\n\nif no color is specified, a random color is used and the default radius is ramped over the zoom level:\n![](images/screenshots/points.png)\n\nHere is the same dataset, but with _point clustering_ enabled:\n\n```ts\nhelpers.addPoint(map, {\n  data: \"public-schools.geojson\",\n  cluster: true,\n});\n```\n\nOn the other hand, if clusters are enabled, the default color is fueled by the color ramp `TURBO` scaled from `10` to `10000` non-linearly resampled with the method `\"ease-out-square\"`. The size also varies from `minPointradius` (default: `10`) to `maxPointRadius` (default: `50`):\n![](images/screenshots/points-clustered.png)\n\nWith the point helper, it's also possible to adapt the color and the radius based on a property. In the following example, we display a point for each public school, with the scaling factor being the number of students:\n\n```ts\nhelpers.addPoint(map, {\n  data: \"public-schools.geojson\",\n  property: \"students\",\n  pointColor: ColorRampCollection.PORTLAND.scale(200, 2000).resample(\"ease-out-sqrt\"),\n  pointOpacity: 0.8,\n  minPointRadius: 6,\n  maxPointRadius: 30,\n  showLabel: true,\n  zoomCompensation: false,\n});\n```\n\n![](images/screenshots/nyc-schools.png)\n\nHere, the`PORTLAND` color ramp is going to be used so that schools with `200` students or less will have the colors at the very beginning of the color ramp and schools with `2000` or more will have the color defined at the very end. Schools in between will be attributed a color in a non-linear fashion, following the `\"ease-out-sqrt\"` method (read **Color Ramps** section above for more info).\n\nAll the other options are documented on [our reference page](https://docs.maptiler.com/sdk-js/api/helpers/#point) and more examples are available [here](https://docs.maptiler.com/sdk-js/examples/?q=point+helper).\n\n#### Heatmap Layer Helper\n\nThe heatmap layer is a great alternative for visualizing a collection of sparse data, but it can be challenging to use, especially when one has to come up with their own color ramp from scratch. **The helper makes this much easier!**\n\nHere is a minimalist example, using the default built-in `TURBO` color ramp:\n\n```ts\nhelpers.addHeatmap(map, {\n  data: \"public-schools.geojson\",\n});\n```\n\n![](images/screenshots/heatmap-schools.png)\n\nSome visualizations are created with a fixed geographic extent or zoom level in mind, whether it's a survey at the scale of a single neighborhood or statistics at a country scale. In this case, we want to tailor the color, radius, weight and intensity of the heatmap blobs exactly for these precise settings. In the following example, we disable the _zoom compensation_ to make sure radio and intensity are never zoom-dependant:\n\n```ts\nhelpers.addHeatmap(map, {\n  data: \"public-schools.geojson\",\n  property: \"students\",\n  // radius: how wide are the blobs\n  radius: [\n    { propertyValue: 100, value: 15 },\n    { propertyValue: 800, value: 50 },\n  ],\n  // weight: how intense are the blob, as fueled by a property\n  weight: [\n    { propertyValue: 100, value: 0.1 },\n    { propertyValue: 800, value: 1 },\n  ],\n  // A custom color ramp, must be used with its default interval of [0, 1]\n  colorRamp: ColorRampCollection.MAGMA,\n  zoomCompensation: false,\n  opacity: 0.6,\n  // a global factor applied to all the blobs, regardless of the property or zoom\n  intensity: 1.2,\n});\n```\n\n![](images/screenshots/heatmap-colorramp.png)\nTurning off _zoom compensation_ allows for more accurate adjustments to the visualization at a specific zoom level, but it may not adapt as smoothly when zooming in or out.\n\nAll 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).\n\n### Other helpers\n\n#### Convert GPX and KML to GeoJSON\n\nIn the [Polyline helper section](#polyline-layer-helper) above, we have seen that one can feed the helper directly with a path to a GPX or KML file, that is then converted under the hood client-side into a GeoJSON `FeatureCollection` object. This conversion feature is also exposed and can be used as such:\n\n```ts\nimport { gpx } from \"@maptiler/sdk\";\n\n// ... assuming inside an async function\n\n// Fetching the GPX file as a string:\nconst gpxFilePath = \"some_gps_trace.gpx\";\nconst gpxResponse = await fetch(gpxFilePath);\nconst gpxStr = await res.text();\n\n// Converting the GPX payload into a GeoJSON FeatureCollection:\nconst features = maptilersdk.gpx(gpxStr);\n```\n\nAnd for KML files:\n\n```ts\nimport { kml } from \"@maptiler/sdk\";\n\n// ... assuming inside an async function\n\n// Fetching the KML file as a string:\nconst kmlFilePath = \"some_gps_trace.kml\";\nconst kmlResponse = await fetch(kmlFilePath);\nconst kmlStr = await res.text();\n\n// Converting the KML payload into a GeoJSON FeatureCollection:\nconst features = maptilersdk.gpx(kmlStr);\n```\n\n#### Take Screenshots, programmatically\n\nThere 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.\n\n**1. Get a `blob` of a screenshot, PNG encoded:**\n\n```ts\nimport { Map, helpers } from \"@maptiler/sdk\";\n\n// ... initialize a Map instance, wait for the \"load\" or \"ready\" event ...\n\n// Inside an async function, or with using .then()\nconst blob = await helpers.takeScreenshot(map);\n```\n\nThe 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.\n\n**2. Download a PNG file:**\n\n```ts\nimport { Map, helpers } from \"@maptiler/sdk\";\n\n// ... initialize a Map instance, wait for the \"load\" or \"ready\" event ...\n\n// No need to be inside an async function, the download will be triggered when the file is ready\nmaptilersdk.helpers.takeScreenshot(map, {\n  download: true,\n  filename: \"map_screenshot.png\",\n});\n```\n\nGetting 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.\n\n\u003e 📣 _**Note:**_ Keep in mind that MapTiler 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/).\n\n### Caching\n\nStarting from v2, MapTiler SDK introduced the **caching** of tiles and fonts served by MapTiler, 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.\n\nCaching greatly improves the performance at load time and positively impacts the user experience, for this reason, it is **enabled by default**. If for debugging purposes or for a very specific use-case caching needs to be disabled, then it is possible:\n\n```ts\nimport { config } from \"@maptiler/sdk\";\n\nconfig.caching = false;\n```\n\n### Easy access to MapTiler API\n\nOur map SDK is not only about maps! We also provide plenty of wrappers to our API calls!\n\n\u003e 📣 _**Note:**_ If you need \u003cins\u003eonly the API Client library\u003c/ins\u003e to use in a headless fashion and without any map display, check out out [API Client library](https://docs.maptiler.com/client-js/) for browser and NodeJS. It's exactely what is down below and only that, in a minimalistic [TypeScript package](https://github.com/maptiler/maptiler-client-js) 🐙.\n\n#### 🔍 Geocoding\n\n\u003e ✅ Please, use geocoding functions only from client-side (browser) and do not 🚫 **store** or **redistribute** MapTiler API data. In case of doubt, consult the [terms](https://www.maptiler.com/cloud/terms/#explicitly-prohibited-use) ⚖️\n\n##### Forward\n\nYou want to know the longitude and latitude of a specific place, use the forward geocoding:\n\n```ts\n// in an async function, or as a 'thenable':\nconst result = await maptilersdk.geocoding.forward(\"paris\");\n```\n\nYou can provide some options such as:\n\n- the proximity, given a lon-lat position, to sort the results\n- one of more languages to get the results into\n- a bounding geo box, to restrict the search to a given window\n\nRead more about forward geocoding on our [official documentation](https://docs.maptiler.com/client-js/geocoding/#forward).\n\n##### Reverse\n\nYou want to know the name of a place, given a longitude-latitude? Use the reverse geocoding:\n\n```ts\n// in an async function, or as a 'thenable':\nconst result = await maptilersdk.geocoding.reverse([6.249638, 46.402056]);\n```\n\nThe same option object as the forward geocoding can be provided.\n\nRead more about reverse geocoding on our [official documentation](https://docs.maptiler.com/client-js/geocoding/#reverse).\n\n##### Language\n\nFor both _forward_ and _reverse_ geocoding, this library provides a list of supported languages as shorthands to [ISO language codes](https://en.wikipedia.org/wiki/ISO_639-1). The result will be provided in multiple languages if the `language` option is an array:\n\n```ts\nconst result = await maptilersdk.geocoding.forward(\"paris\", { language: [maptilersdk.geocoding.languages.SPANISH, maptilersdk.geocoding.languages.KOREAN] });\n```\n\nThe special language `AUTO` will detect the platform/browser preferred language.\n\n#### 🕵️‍♂️ Geolocation\n\nThe geolocation service provides location information of a visitor using its IP address.\n\nThe geolocation uses the IP address of a visitor to provide information about their location, such as city, region, country, timezone, etc. The precision is lower than GPS but does not require visitors to explicitly enable the location service from their web browser.\n\nThere is only a single function:\n\n```ts\n// in an async function, or as a 'thenable':\nconst result = await maptilersdk.geolocation.info();\n```\n\nRead more about geolocation on our [official documentation](https://docs.maptiler.com/client-js/geolocation/).\n\n#### 🌐 Coordinates\n\nIf you are already familiar with [epsg.io](https://epsg.io/) (created by MapTiler), then you may find it convenient to access the details of more than 10 thousands coordinate reference systems (CRS) programmatically, as well as transform coordinates from one system to another!\n\n##### Search\n\nThe `search` lets you perform a query in a free-form fashion. Here are some examples:\n\n```ts\n// in an async function, or as a 'thenable':\nconst resultA = await maptilersdk.coordinates.search('mercator');\nconst resultB = await maptilersdk.coordinates.search('plate carree');\nconst resultC = await maptilersdk.coordinates.search('france');\nconst resultD = await maptilersdk.coordinates.search('code:4326', {transformations: true}));\n```\n\nThe `transformations` options retrieve a lot more details about the CRS that MapTiler API is able to transform to/from than just their IDs.\n\nRead more about searching coordinate systems on our [official documentation](https://docs.maptiler.com/client-js/coordinates/#search).\n\n##### Transform\n\nTransforming a couple of coordinates from one system to another can be challenging, for example, most countries have their own official system, yet web mapping tools are more often than not exclusive to [WGS84](https://epsg.io/4326).\n\nIf not provided, both the source (`sourceCrs`) and the destination (`targetCrs) are defaulted to **EPSG:4326\\*\\*** (in other words, [WGS84](https://epsg.io/4326)). Here is how to use this feature:\n\n```ts\n// in an async function, or as a 'thenable':\n\n// Providing one coordinate to transform, with a target CRS being EPSG:9793 (RGF93 v2 / Lambert-93, France official CRS)\nconst resultA = await maptilersdk.coordinates.transform([1, 45], { targetCrs: 9793 });\n\n// Using the same logic, we can pass up to 50 coordinates to be transformed\nconst resultB = await maptilersdk.coordinates.transform(\n  [\n    [10, 48],\n    [1, 45],\n  ],\n  { targetCrs: 9793 },\n);\n```\n\nRead more about transforming coordinates on our [official documentation](https://docs.maptiler.com/client-js/coordinates/#transform).\n\n#### 💽 Data\n\nMapTiler gives its users the possibility to [upload and create data](https://cloud.maptiler.com/data/), manually with a user interface or by uploading a GPX, GeoJSON, KML or shp file. A unique ID is associated with each dataset so that we can later access it programmatically to retrieve a GeoJSON equivalent of it:\n\n```ts\n// in an async function, or as a 'thenable':\nconst result = await maptilersdk.data.get(\"my-dataset-unique-id\");\n```\n\nSince the result is a GeoJSON, it can easily be added to a `map` with `.addSource()` and `.addLayer()`.\n\nRead more about fetching your own data on our [official documentation](https://docs.maptiler.com/client-js/data/).\n\n#### 🗺️ Static maps\n\n\u003e ✅ Please, use static maps URLs only from client side `\u003cimg\u003e` elements, and do not 🚫 store or redistribute the static map files. In case of doubt, consult the [terms](https://www.maptiler.com/cloud/terms/#explicitly-prohibited-use) ⚖️\n\nMaptiler provides many possibilities for creating static maps as PNG, JPEG or WebP images. They all offer the possibilities to:\n\n- Choose from one of the MapTiler styles or your own\n- Add markers with a custom icon (or default icon with a custom color)\n- Add a path or polygon, with a parametric line width and color and a parametric filling color\n\nThree modes are available: `centered`, `bounded` and `automatic`.\n\n\u003e 📣 _**important:**_ \u003cspan style=\"text-decoration: underline\"\u003eonly image **URLs** are returned.\u003c/span\u003e  \n\u003e Contrary to the other functions of this library, the static map functions **do not** perform any query to MapTiler API, instead they build the image URL for you to use in `\u003cimg\u003e` elements.\n\n##### Map Styles\n\nIn the following static map functions, the `option` object features a `style` property that can be a string or one of the built-in style shorthand. Here is the full list:\n\n- `MapStyle.STREETS`, reference style for navigation and city exploration\n  - `MapStyle.STREETS.DARK` (variant)\n  - `MapStyle.STREETS.LIGHT` (variant)\n  - `MapStyle.STREETS.NIGHT` (variant)\n  - `MapStyle.STREETS.PASTEL` (variant)\n- `MapStyle.OUTDOOR` reference style for adventure\n  - `MapStyle.OUTDOOR.DARK` (variant)\n- `MapStyle.WINTER` reference style for winter adventure\n  - `MapStyle.WINTER.DARK` (variant)\n- `MapStyle.SATELLITE` reference style satellite and airborne imagery (no variants)\n- `MapStyle.HYBRID` reference style satellite and airborne imagery with labels (no variants)\n- `MapStyle.BASIC` reference style for minimalist design and general purpose\n  - `MapStyle.BASIC.DARK` (variant)\n  - `MapStyle.BASIC.LIGHT` (variant)\n- `MapStyle.BRIGHT` reference style for high-contrast navigation\n  - `MapStyle.BRIGHT.DARK` (variant)\n  - `MapStyle.BRIGHT.LIGHT` (variant)\n  - `MapStyle.BRIGHT.PASTEL` (variant)\n- `MapStyle.TOPO` reference style for topographic study\n  - `MapStyle.TOPO.SHINY` (variant)\n  - `MapStyle.TOPO.PASTEL` (variant)\n  - `MapStyle.TOPO.TOPOGRAPHIQUE` (variant)\n- `MapStyle.VOYAGER` reference style for stylish yet minimalist maps\n  - `MapStyle.VOYAGER.DARK` (variant)\n  - `MapStyle.VOYAGER.LIGHT` (variant)\n  - `MapStyle.VOYAGER.VINTAGE` (variant)\n- `MapStyle.TONER` reference style for very high contrast stylish maps\n  - `MapStyle.TONER.BACKGROUND` (variant)\n  - `MapStyle.TONER.LITE` (variant)\n  - `MapStyle.TONER.LINES` (variant)\n- `MapStyle.OPENSTREETMAP` (reference style, this one does not have any variants)\n- `MapStyle.STAGE`, the perfect style for data visualization, with very little noise\n  - `MapStyle.STAGE.DARK` (variant)\n  - `MapStyle.STAGE.LIGHT` (variant)\n\n##### Centered static maps\n\nThis type of map is centered on a longitude-latitude coordinate and the zoom level must also be provided (from `0`: very zoomed out, to `22`: very zoomed in).  \nNote that if a path or markers are provided, the framing of the map will not automatically adapt to include those (use the `automatic` mode for that).\n\n```ts\nconst imageLink = maptilersdk.staticMaps.centered(\n  // center position (Boston)\n  [-71.0608, 42.362114],\n\n  // zoom level\n  12.5,\n\n  // Options\n  {\n    // Request a hiDPI/Retina image\n    hiDPI: true,\n\n    // Output image size\n    width: 1000,\n    height: 1000,\n\n    // Map style\n    style: maptilersdk.MapStyle.OUTDOOR,\n  },\n);\n```\n\nRead more about centered static maps on our official [API documentation](https://docs.maptiler.com/cloud/api/static-maps/#center-based-image).\n\n##### Bounded static maps\n\nThis type of map requires a bounding box made of two points: the south-west bound and the north-east bound. The zoom level cannot be provided and is automatically deduced from the size of the bounding box.\n\n```ts\nconst imageLink = maptilersdk.staticMaps.bounded(\n  // The bounding box on Europe\n  [\n    -24, // west bound (min x)\n    34.5, // south bound (min y)\n    32, // east bound (max x)\n    71, // north bound (max y)\n  ],\n\n  // Options\n  {\n    hiDPI: true,\n    width: 2048,\n    height: 2048,\n    style: maptilersdk.MapStyle.STREETS.DARK,\n\n    // Extra space that will add around the bounding box, in percentage\n    // (0.1 = 10% is actually the dafault)\n    padding: 0.1,\n  },\n);\n```\n\nSince the zoom level cannot be provided, the level of details is dictated by the size of the output image. here is an example:\n\n|                      `2048 x 2048`                      |                      `1024 x 1024`                      |\n| :-----------------------------------------------------: | :-----------------------------------------------------: |\n| ![](images/screenshots/static-bounded-europe-2048.jpeg) | ![](images/screenshots/static-bounded-europe-1024.jpeg) |\n\nAs you may notice, the geo bounding box could have very different proportions than the output image size. In the following example, we place the very same bounding box around Portugal, which has a this particular strip looking shape. We also add a `path` that repeats exactly the same bounding box to show the difference between the provided bounding box and the final image. We kept the default padding of 10%:\n\n|                         `2048 x 2048`                          |                         `1024 x 2048`                          |\n| :------------------------------------------------------------: | :------------------------------------------------------------: |\n| ![](images/screenshots/static-bounded-portugal-2048x2048.jpeg) | ![](images/screenshots/static-bounded-portugal-1024x2048.jpeg) |\n\nRead more about bounded static maps on our official [API documentation](https://docs.maptiler.com/cloud/api/static-maps/#bounds-based-image).\n\n##### Automatic static maps\n\nAs we have seen with centered and bounded maps, providing all the parameters is nice but can be cumbersome for the simplest use cases. This is why MapTiler also provides static maps that fit automatically whatever you need to place inside: path or markers.\n\nIn the following example, we are going to load a cycling track recorded by one of our team members in Montreal, Canada. The track, originally a GPX file, was pushed to MapTiler Data and is now made available as a GeoJSON:\n\n```ts\n// Fetching the GeoJSON\nconst bikeTrack = await maptilersdk.data.get(\"the-id-of-a-bike-track-in-montreal\");\n\n// Extracting the track points with the shape [[lng, lat], [lng, lat], ...]\nconst trackPoints = bikeTrack.features[0].geometry.coordinates[0].map((p) =\u003e p.slice(0, 2));\n\nconst imageLink = maptilersdk.staticMaps.automatic({\n  // hiDPI/Retina precision\n  hiDPI: true,\n\n  // A fairly large output image\n  width: 2048,\n  height: 1024,\n\n  // A grey style on which the track will pop!\n  style: maptilersdk.MapStyle.STREETS.LIGHT,\n\n  // Draw a path with the trackpoints\n  path: trackPoints,\n\n  // Adding a marker for the starting point, with a custom color (array of shape [lng, lat, color])\n  markers: [trackPoints[0][0], trackPoints[0][1], \"#0a0\"],\n\n  // Showing the track in red\n  pathStrokeColor: \"red\",\n});\n```\n\nAnd voila!\n\n![static map with bike path](images/screenshots/static-with-path.jpeg)\n\n\u003e 📣 _**Note:**_ The GeoJSON for this track contains 9380 couples of coordinates, which is a lot! In order to send the track to MapTiler static maps API, the client simplifies the long paths while keeping a high degree of precision using a very fast [Ramer-Douglas-Peucker algorithm](https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm).\n\nRead more about bounded static maps on our official [API documentation](https://docs.maptiler.com/cloud/api/static-maps/#auto-fitted-image).\n\n#### 🏔️ Elevation\n\nWith the elevation API, it's possible to get the elevation in meters from any location. It's possible to lookup and compute elevation for a single location, to provide a batch of points, from a GeoJSON LineString or a GeoJSON MultiLineString!\n\n\u003e ℹ️ Under the hood, the elevation API is fueled by MapTiler **RGB Terrain** raster tileset, which is a composite of many high-resolution DEMs from all over the world, currated and processed by our geodata team! The same dataset is also fueling our SDK's elevation (3D terrain) and the hillshading we use in many of our styles.\n\n\u003e 📣 Note for **TypeScript** users: internaly, the elevation feature relies on some _GeoJSON_ types definitions that can be found in this NPM package: `@types/geojson`. Namely `LineString`, `MultiLineString` and `Position`. It may improve your developer experience to also use these types.\n\nLet's see how to use it:\n\n##### At a single location\n\n```ts\n// Not mandatory, but it's to explain where the type comes from:\nimport { Position } from \"geojson\";\n\nconst montBlancPeak: Position = [6.864884, 45.832743];\nconst elevatedPosition = await maptilersdk.elevation.at(montBlancPeak);\n```\n\nThe returned value is also a _GeoJSON_ `Position` array, but with three elements: `[lng, lat, elevation]`.\n\nRead more about elevation lookup for a single location in our [official documentation](https://docs.maptiler.com/client-js/elevation/#at).\n\n##### Batch mode\n\n```ts\n// Not mandatory, but it's to explain where the type comes from:\nimport { Position } from \"geojson\";\n\nconst peaks: Position[] = [\n  [6.864884, 45.832743], // Mont Blanc, Alps\n  [86.925, 27.9881], // Mount Everest, Himalayas\n  [-70.0109, -32.6532], // Aconcagua, Andes\n  [-151.0064, 63.0695], // Denali, Alaska\n  [37.3556, -3.0674], // Mount Kilimanjaro\n  [42.4453, 43.3499], // Mount Elbrus, Caucasus\n  [137.1595, -4.0784], // Puncak Jaya, Sudirman Range\n  [-140.4055, 60.5672], // Mount Logan, Saint Elias Mountains\n  [138.73111, 35.358055], // Mount Fuji\n];\n\nconst elevatedPeaks = await maptilersdk.elevation.batch(peaks);\n```\n\nRead more about elevation lookup for a batch of locations in our [official documentation](https://docs.maptiler.com/client-js/elevation/#batch).\n\n##### From a GeoJSON LineString\n\nIn the _GeoJSON_ LineString case, it clones the entire structure and the position arrays of the clone will contain three elements: `[lng, lat, elevation]`. The original LineString is not mutated nor pointed at.\n\n```ts\n// Not mandatory, but it's to explain where the type comes from:\nimport { LineString } from \"geojson\";\n\nconst someLineString: LineString = {\n  type: \"LineString\",\n  coordinates: [\n    [6.864884, 45.832743],\n    [86.925, 27.9881],\n    [-70.0109, -32.6532],\n  ],\n};\n\nconst someElevatedLineString = await maptilersdk.elevation.fromLineString(someLineString);\n// someElevatedLineString is also of type LineString\n```\n\nRead more about elevation lookup for a `LineString` in our [official documentation](https://docs.maptiler.com/client-js/elevation/#linestring).\n\n##### From a GeoJSON MultiLineString\n\nIn the _GeoJSON_ MultiLineString case, it clones the entire structure and the position arrays of the clone will contain three elements: `[lng, lat, elevation]`. The original MultiLineString is not mutated nor pointed at.\n\n```ts\n// Not mandatory, but it's to explain where the type comes from:\nimport { MultiLineString } from \"geojson\";\n\nconst someMultiLineString: MultiLineString = {\n  type: \"LineString\",\n  coordinates: [\n    [\n      [6.864884, 45.832743],\n      [86.925, 27.9881],\n      [-70.0109, -32.6532],\n    ],\n    [\n      [-151.0064, 63.0695],\n      [37.3556, -3.0674],\n      [42.4453, 43.3499],\n    ],\n    [\n      [137.1595, -4.0784],\n      [-140.4055, 60.5672],\n      [138.73111, 35.358055],\n    ],\n  ],\n};\n\nconst someElevatedMultiLineString = await maptilersdk.elevation.fromMultiLineString(someMultiLineString);\n// someElevatedMultiLineString is also of type MultiLineString\n```\n\nRead more about elevation lookup for a `MultiLineString` in our [official documentation](https://docs.maptiler.com/client-js/elevation/#multilinestring).\n\n##### Caching\n\nIn order to increase performance while reducing unnecessary elevation data fetching, the elevation tiles are cached. This is particularly important for the LineString and MultiLineString lookups because GeoJSON data are likely to come from a recorded or planned route, where position points are very close to one another.\n\n#### 🧮 Math\n\nSome operations can be fairly repetitive: WGS84 to Mercator, WGS84 to _zxy_ tile index, the distance between two points with the Haversine formula, etc. As a result, we have decided to expose a `math` package providing the most recurrent feature, so that, just like us at MapTiler, you no longer need to copy-paste the same function from your previous project!\n\nThe `math` package differs from the others in the sense that it does not call the MapTiler API, instead, it operates fully on the machine it's running on.\n\nHere are some examples:\n\n```ts\n// Not mandatory, but it's to explain where the type comes from:\nimport { Position } from \"geojson\";\n\n// Some constants\nconst earthRadius = maptilersdk.math.EARTH_RADIUS;\nconst earthCircumference = maptilersdk.math.EARTH_CIRCUMFERENCE;\n\nconst montBlancPeakWgs84: Position = [6.864884, 45.832743];\n\n// From WGS84 to Mercator\nconst montBlancPeakMerc = maptilersdk.math.wgs84ToMercator(montBlancPeakWgs84); // also of type Position\n\n// From Mercator to WGS84\nconst montBlancPeakWgs84Again = maptilersdk.math.mercatorToWgs84(montBlancPeakMerc);\n\n// A great-circle distance in meter:\nconst from: Position = /* ... */;\nconst to: Position = /* ... */;\nconst distance = maptilersdk.math.haversineDistanceWgs84(from, to);\n\n// Full distance of a route made of many positions\nconst route: Position[] = /* ... */;\nconst totalDistance = maptilersdk.math.haversineCumulatedDistanceWgs84(route);\n\n// Lon lat to tile index, given a zoom level. An [x, y] array is returned\nconst tileXY = maptilersdk.math.wgs84ToTileIndex(montBlancPeakWgs84, 14);\n// Possible to have floating point tile indices with a third argument to `false`\n\n// and many more!\n```\n\nPlease find out more about the math package in our [official documentation](https://docs.maptiler.com/client-js/math):\n\n### Telemetry\n\nThe telemetry is very valuable to the team at MapTiler because it shares information about where to add the extra effort. It also helps spotting some incompatibility issues that may arise between the SDK and a specific version of a module.\n\nIt consists in sending metrics about usage of the following features:\n\n- SDK version [string]\n- API key [string]\n- MapTiler sesion ID (if opted-in) [string]\n- if tile caching is enabled [boolean]\n- if language specified at initialization [boolean]\n- if terrain is activated at initialization [boolean]\n- if globe projection is activated at initialization [boolean]\n\nIn addition, each official module will be added to a list, alongside its version number.\n\nTelemetry is enabled by default but can be opted-out by setting `telemetry` value of `config` to `false`.\n\n```ts\nimport * as maptilersdk from \"@maptiler/sdk\";\n\nmaptilersdk.config.telemetry = false;\n```\n\n\u003cbr\u003e\n\n## Migration Guide\n\n- [How to migrate/switch from Mapbox to MapTiler](https://docs.maptiler.com/sdk-js/examples/switch-from-mapbox/)\n- [How to migrate/switch from MapLibre to MapTiler](https://docs.maptiler.com/sdk-js/examples/switch-from-maplibre/)\n\n\u003cbr\u003e\n\n## 💬 Support\n\n- 📚 [Documentation](https://docs.maptiler.com/sdk-js/) - Comprehensive guides and API reference\n- ✉️ [Contact us](https://maptiler.com/contact) - Get in touch or submit a request\n- 🐦 [Twitter/X](https://twitter.com/maptiler) - Follow us for updates\n\n\u003cbr\u003e\n\n---\n\n\u003cbr\u003e\n\n## 🤝 Contributing\n\nWe love contributions from the community! Whether it's bug reports, feature requests, or pull requests, all contributions are welcome:\n\n- Fork the repository and create your branch from `main`\n- If you've added code, add tests that cover your changes\n- Ensure your code follows our style guidelines\n- Give your pull request a clear, descriptive summary\n- Open a Pull Request with a comprehensive description\n\n\u003cbr\u003e\n\n## 📄 License\n\nThis project is licensed under the BSD 3-Clause License – see the [LICENSE](./LICENSE) file for details.\n\n\u003cbr\u003e\n\n## 🙏 Acknowledgements\n\nThis project is built on the shoulders of giants:\n\n- [MapLibre GL JS](https://maplibre.org/) – The open-source mapping library\n- [OpenStreetMap](https://openstreetmap.org/) – The free wiki world map\n\n\u003cbr\u003e\n\n\u003cp align=\"center\" style=\"margin-top:20px;margin-bottom:20px;\"\u003e \u003ca href=\"https://cloud.maptiler.com/account/keys/\" style=\"display:inline-block;padding:12px 32px;background:#F2F6FF;color:#000;font-weight:bold;border-radius:6px;text-decoration:none;\"\u003e Get Your API Key \u003csup style=\"background-color:#0000ff;color:#fff;padding:2px 6px;font-size:12px;border-radius:3px;\"\u003eFREE\u003c/sup\u003e\u003cbr /\u003e \u003cspan style=\"font-size:90%;font-weight:400;\"\u003eStart building with 100,000 free map loads per month ・ No credit card required.\u003c/span\u003e \u003c/a\u003e \u003c/p\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e 💜 Made with love by the \u003ca href=\"https://www.maptiler.com/\"\u003eMapTiler\u003c/a\u003e team \u003cbr /\u003e\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://www.maptiler.com/interactive-maps/\"\u003eWebsite\u003c/a\u003e •\n  \u003ca href=\"https://docs.maptiler.com/sdk-js/\"\u003eDocumentation\u003c/a\u003e •\n  \u003ca href=\"https://github.com/maptiler/maptiler-sdk-js/\"\u003eGitHub\u003c/a\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaptiler%2Fmaptiler-sdk-js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaptiler%2Fmaptiler-sdk-js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaptiler%2Fmaptiler-sdk-js/lists"}