{"id":28576953,"url":"https://github.com/maptiler/maptiler-marker-layout","last_synced_at":"2025-06-11T00:08:25.337Z","repository":{"id":244927342,"uuid":"746760007","full_name":"maptiler/maptiler-marker-layout","owner":"maptiler","description":"Popup manager for MapTiler SDK","archived":false,"fork":false,"pushed_at":"2025-02-28T16:34:48.000Z","size":47499,"stargazers_count":5,"open_issues_count":2,"forks_count":0,"subscribers_count":6,"default_branch":"main","last_synced_at":"2025-05-06T22:17:03.815Z","etag":null,"topics":["maptiler-sdk-plugin"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","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.md","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}},"created_at":"2024-01-22T16:12:18.000Z","updated_at":"2025-01-08T10:26:10.000Z","dependencies_parsed_at":"2024-12-13T15:45:53.810Z","dependency_job_id":"fae91567-6f6a-4a1f-bf08-7440b7a62016","html_url":"https://github.com/maptiler/maptiler-marker-layout","commit_stats":null,"previous_names":["maptiler/maptiler-marker-layout"],"tags_count":4,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-marker-layout","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-marker-layout/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-marker-layout/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-marker-layout/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/maptiler","download_url":"https://codeload.github.com/maptiler/maptiler-marker-layout/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/maptiler%2Fmaptiler-marker-layout/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259172986,"owners_count":22816560,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["maptiler-sdk-plugin"],"created_at":"2025-06-11T00:08:24.835Z","updated_at":"2025-06-11T00:08:25.306Z","avatar_url":"https://github.com/maptiler.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n\u003ca href=\"https://docs.maptiler.com/sdk-js/modules/marker-layout\"\u003eofficial page →\u003c/a\u003e\u003cbr\u003e\n  \u003cimg src=\"images/maptiler-logo.svg\" width=\"300px\"\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\" style=\"color: #AAA\"\u003e\n  Better and customizable markers for \u003ca href=\"https://www.maptiler.com/cloud/\"\u003eMapTiler Cloud\u003c/a\u003e and \u003ca href=\"https://docs.maptiler.com/sdk-js\"\u003eMapTiler SDK JS\u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"images/JS-logo.svg\" width=\"20px\"\u003e\n  \u003cimg src=\"images/TS-logo.svg\" width=\"20px\"\u003e\n  \u003cimg src=\"https://img.shields.io/npm/v/@maptiler/marker-layout\"\u003e\u003c/img\u003e\n  \u003cimg src=\"https://img.shields.io/twitter/follow/maptiler?style=social\"\u003e\u003c/img\u003e\n\u003c/p\u003e\n\n# MapTiler Marker layout for MapTiler SDK\nThe Marker Layout is a helper to create non-coliding marker overlays on top of MapTiler SDK. Fed by a **vector layer** from a tileset or from a GeoJSON source, it can be tuned with plenty of options.\nThanks to its non opinionated and logic-only approach, it lets you bind any kind of rendering you wish for your markers: vanilla HTML Divs, React components, floating canvas, etc. as it computes the position and size of the markers but lets you take handle the rendering part.\n\n## Some Examples\nHere is only a few examples of what's possible with fairly basic HTML markers.\n\nWith markers anchored to the city layers, directly fueled by the `streets-v2` style from MapTiler Cloud:\n![](images/cities.png)\n\nDisplaying weather data is also a nice usecase. For this, we anchor the markers to cities, towns and villages from an official MapTiler Cloud style and then we asynchronously fetch the weather data using [MapTiler Weather library](https://www.maptiler.com/weather/), for each vector features using their coordinates:\n![](images/weather_europe_large.png)\n![](images/weather_usa_large.png)\n\n**But markers don't need to look like markers!** Smaller markers with transparent background are a nice way to avoid cluter. Icons are SVG animated:\n![](images/weather_minimal.png)\n\nSince markers are overlaying on top of a map, it's generally a good practice to keep them small, so that the basemap remains readable, but **Marker Layout** does not technically enforce that.\n\n## Some Concepts\nThe Marker Layout...\n- computes screen-space bounding box logic\n- can be provided the desired marker size and relative anchor point\n- is fed with one or multiple vector layer\n- can only use *point* features\n- create non-overlapping bounding boxes\n- can filter and sort features based on vector feature properties\n- sorting can be done with a function, so that rank can come from an external source\n- can group multiple vector features into each marker\n- when updated will retrieve three lists of markers relative to the previous state: the new, the removed and the moved markers\n- does not enforce how the the actual visual markers (eg. divs) should be created, cached, pooled, reused or deleted\n\n## Usage\nTo install it:\n```bash\nnpm install @maptiler/marker-layout\n```\nThen, import it:\n\n```ts\nimport { MarkerLayout } from \"@maptiler/marker-layout\";\n\n...\n\nconst markerLayout = new MarkerLayout(map, options);\n```\n\n\nOr it can be used from MapTiler Cloud CDN with vanilla JS:\n\n```html\n\u003cscript src=\"https://cdn.maptiler.com/maptiler-marker-layout/v1.0.0/maptiler-marker-layout.umd.js\"\u003e\u003c/script\u003e\n```\nAnd then be address as such:\n```js\nconst markerLayout = new maptilermarkerlayout.MarkerLayout(map, options);\n```\n\n### Options\nHere are all the options available:\n```ts\n{\n  /**\n   * IDs of layers to query for vector features.\n   * Default: uses all the layers available\n   */\n  layers?: Array\u003cstring\u003e;\n\n  /**\n   * Size of the markers on screen space [width, height].\n   * Default: `[150, 50]`\n   */\n  markerSize?: [number, number];\n\n  /**\n   * Maximum number of markers to keep.\n   * Default: no maximum\n   */\n  max?: number;\n\n  /**\n   * Position of the marker relative to its anchor point.\n   * Default: `\"center\"`\n   */\n  markerAnchor?: MarkerAnchor;\n\n  /**\n   * Offset to apply to the marker, in number of pixel, relative to its anchor position.\n   * First element of the array is the horizontal offset where negative shifts towards\n   * the left and positive shifts towards the right.\n   * Second element of the array is the vertical offset where negative shifts towards\n   * the top and positive shifts towards the bottom.\n   * Default: `[0, 0]`\n   */\n  offset?: [number, number];\n\n  /**\n   * A filter function can be provided. Each feature will be tested against this filter function,\n   * and the returned value can be `true` (the feature is kept) or `false` (the feature is discarded).\n   * Default: none\n   */\n  filter?: (feature: MapGeoJSONFeature) =\u003e boolean;\n\n  /**\n   * Property to sort the features by. If not provided, the features will not be sorted.\n   * Default: not provided\n   */\n  sortingProperty?: string,\n\n  /**\n   * Property to sort the features by. If not provided, the features will not be sorted.\n   * Alternatively, the sorting property can be a function that takes the feature as\n   * argument and returns a number, aka. the sorting value (or rank)\n   * Default: not provided\n   */\n  sortingProperty?: string | ((feature: MapGeoJSONFeature) =\u003e number);\n\n  /**\n   * Property to group by. The property must be present in the `properties` object of the feature\n   * unless the value of `groupBy` is equal to `\"coordinates\"`, then the geometry coordinates are\n   * being used.\n   * Default: no grouping\n   */\n  groupBy?: string,\n\n  /**\n   * Markers can contain multiple vector features, this parameter can be set to have a strict limit.\n   * Default: `Infinity`\n   */\n  maxNbFeaturesPerMarker?: number,\n\n  /**\n   * When a marker contains multiple features, its size can get bigger. This number is the max ratio applied to the\n   * defined `markerSize`. Intentionnaly non-integer so that the user can see there is still half an element to\n   * show at the bottom and undestand they can scroll for more.\n   * Default: `2.5`\n   */\n  maxRatioUnitSize?: number,\n}\n\n// The possible anchor points\ntype MarkerAnchor = \"center\" | \"top\" | \"bottom\" | \"left\" | \"right\";\n```\n\n## API\nAppart from the constructor, there are a few things to get familiar with.\n\n### AbstractMarker\nThey are simple data structure that hold informations about a marker (position, size)\nand the list of vector features it is supposed to contain. Here is how it looks like:\n\n```ts\ntype AbstractMarker = {\n  /**\n   * Unique ID of a marker, most likely the ID of a geojson feature (from a vector tile)\n   */\n  id: number;\n\n  /**\n   * Position in screenspace of the top-left corner [x, y]\n   */\n  position: [number, number];\n\n  /**\n   * Size in screen space [width, height]\n   */\n  size: [number, number];\n\n  /**\n   * The feature represented by the marker\n   */\n  features: MapGeoJSONFeature[];\n\n  /**\n   * Size of each internal elements (useful for when a marker contain multiple vector features)\n   */\n  internalElementSize: [number, number],\n};\n```\nAgain, an *abstract marker* is **not** an actual visual marker. It only aims at providing the information to help making an actual graphic representation of a marker.\n\n### MarkerMap\nThe type `MarkerMap` is simply a [JS Map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map) of `AbstractMarker`. The *key* of this map (*number*) is a hash specific to one of multiple vector features contained by a marker. If you want to add an extra caching logic, you may want to track this ID, otherwise it's only used internaly and is of little interest at application level.\n\n```ts\ntype MarkerMap = Map\u003cnumber, AbstractMarker\u003e;\n```\n\n### MarkerStatus\nAn object of type *markerStatus* is a simple data structure that contain li\n\n```ts\n/**\n * Status of the marker compared to the previous status\n */\ntype MarkerStatus = {\n  /**\n   * The markers that were added since the last update()\n   */\n  new: MarkerMap;\n\n  /**\n   * The markers that were already present in the last update but had their position changed\n   */\n  updated: MarkerMap;\n\n  /**\n   * The markers that are no longer present since the last update\n   */\n  removed: MarkerMap;\n};\n```\n\n### Methods\nAs we interact with the map (pan, zoom, rotation, etc.) we need to know which markers are now visible, disapeared outside the viewport or are still visible but at a different (screen space) location.\n\nTo compute this, a `MarkerLayout` instance has two methods:\n- `.update()` compute a complete new status of markers, returning a `MarkerStatus`.  \nIn case many vector features are found in the specified `layers` with the provided `filter`, this may have an impact on performances and may not be suitable to call from a `map.on(\"move\", () =\u003e { ... })` event.\n\n\n- `.softUpdateAbstractMarker(am)` only update a single `AbstractMarker` with a new screenspace position.  \nThis is convenient to use when there are hundreds of vector features found but we only want to update the position, say, of the ones retrieved with the previous full `.update()` call. In this performance-wise conservative mode, one would typically bind `.update()` to the `Map` event `\"idle\"` and bind `.softUpdateAbstractMarker(am)` to the `Map` event `\"move\"`.\n\nWe can also reset the internal `MarkerStatus` if we need to restart from a blank slate without creating a new `MarkerLayout` instance:\n- `.reset()`\n\n## Examples\nYou can find two examples in this repo:\n- demo using only `.update()` [here](demos/cities.html)\n- demo using both `.update()` and `.softUpdateAbstractMarker()` [here](demos/cities-many.html)\n\n## License\nSee [license.md](license.md)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaptiler%2Fmaptiler-marker-layout","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmaptiler%2Fmaptiler-marker-layout","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmaptiler%2Fmaptiler-marker-layout/lists"}