maplibre/maplibre-agent-skills

maplibre-sprites-icons

Sprites and icon images for MapLibre GL JS — choosing a Marker, a sprite, or addImage; the sprite base URL and its {id, url} array form for several sheets; self-hosting and building sprites; runtime images, SVGs included, with addImage; finding a missing ic…

소스 보기

사용 방법

  1. 설치 명령을 복사하고 자신의 에이전트 환경에 설치하기 전에 소스 패키지를 확인하세요.
  2. 필요한 도구, 인증 정보, 지원 환경은 원본 문서에서 확인하세요.
  3. 입력과 원하는 산출물을 설명하고 출처와 결과를 대조하세요.
npx skills add https://github.com/maplibre/maplibre-agent-skills --skill maplibre-sprites-icons

시작 전 확인

  • 설치에는 모델 구독, API 한도, 파일 접근 권한이 포함되지 않습니다.
  • 별점 수와 설치 수는 활동 지표이며 독립적인 품질·보안 검증이 아닙니다.
원본 Skill 문서

원본 저장소의 제목, 예시, 코드, 표, 링크, 이미지를 유지해 표시합니다.

MapLibre Sprites and Icons

An icon on a MapLibre GL JS map is either part of the map, drawn by a symbol layer from a sprite (a PNG atlas plus a JSON index served from the style's sprite URL) or from an image registered at runtime with addImage, or on top of the map as a Marker, outside the style. For icon color, halos, and figure-ground against imagery, see maplibre-cartography; for text and glyph setup, see maplibre-fonts-glyphs.

When to Use This Skill

  • Choosing between a Marker, a sprite, and addImage for icons on a map
  • A symbol layer's icons do not render
  • Adding your own icon sheet to a style whose sprite you do not control
  • Setting up, self-hosting, or building a sprite from SVGs (for glyphs, see maplibre-fonts-glyphs)
  • Registering icons, including SVGs, for a layer your app adds at runtime
  • Route shields render as bare numbers or missing badges
  • Writing tooling that reads or generates symbol layers and has to recognize a shield

Choosing a Marker, a sprite, or addImage

Two questions, in order; the sections below follow them.

  1. Is the icon part of the map, or on top of it? Part of the map means a symbol layer's icon-image: placed together with the labels, hidden when it collides, and drawn in layer order, for a point dataset of any size. A small number of annotations on top of the map, that people drag or that need their own HTML, are `Marker`s.
  2. For a symbol layer, where does the image come from?
  • The style. POIs, town spots, shields, pattern textures, and any other icon or texture layered into the style, where it takes part in the visual hierarchy and collisions: a sprite. Your own set on a basemap whose sprite you do not control is a second sheet, still a sprite.
  • Your app, at runtime. Icons for a layer the app adds, such as a GeoJSON source, that no sheet carries: `addImage`. Use it deliberately. Each image is its own request and addImage call, where a sprite arrives as one PNG and one JSON, and its icons collide with the style's labels and with each other like sprite icons.

Sprites

The style's sprite value is a base URL with no file extension; MapLibre appends .json, .png, and @2x variants itself.[1] A symbol layer names a sprite image in icon-image (other layers in their *-pattern property), and the name must exactly match an ID in the sprite JSON index or the icon is not drawn.

json
{
  "sprite": "https://example.com/sprites/poi",
  "layers": [
    {
      "id": "cafes",
      "type": "symbol",
      "source": "places",
      "source-layer": "poi",
      "layout": { "icon-image": "cafe" }
    }
  ]
}

https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite works for testing; do not use it in production.

Multiple sprite sheets in one style

sprite is not limited to a single string. Since MapLibre GL JS 3.0 (#1805) it also accepts an array of `{id, url}` objects,[1] so a style can load its own icons alongside a basemap provider's sheet. Before 3.0 a style could name only one sprite, so adding your own icons meant merging them into the provider's sheet; with the array form, each sheet stays where it is hosted.

json
{
  "sprite": [
    { "id": "default", "url": "https://tiles.openfreemap.org/sprites/ofm_f384/ofm" },
    { "id": "my-icons", "url": "https://example.com/sprites/poi" }
  ]
}

Rules that follow from that form:

  • Reference images by `id:image-name` — "icon-image": "my-icons:cafe". An unprefixed "cafe" resolves against the default sheet only.
  • The `id` `default` is the one exception: its images take no prefix, which is what keeps an existing style's icon-image values working when you convert its string sprite to the array form. Give the basemap's sheet id: "default" and the shield and POI layers already in the style keep resolving unchanged.
  • All ids and all URLs must be unique. Duplicates are a validation error (all the sprites' ids must be unique, but <id> is duplicated), not a last-one-wins merge.
  • Each URL is still extension-less — MapLibre appends .json, .png, and the @2x variants per entry, exactly as for the string form.
  • From code, `map.addSprite('my-icons', 'https://example.com/sprites/poi')` adds a sheet once the style has loaded; a string sprite becomes the array's default entry.[3]
  • ❌ { "id": "my-icons", "url": "https://example.com/sprites/poi.json" } → requests poi.json.json
  • ❌ Typing sprite as string in your own tooling: an array value stringifies to [object Object], and the loader rejects it with Invalid sprite URL "[object Object]", must be absolute. Handle both shapes wherever you read a style's sprite.

Self-hosted sprites

To avoid third-party dependencies, copy an existing sprite directory (PNG + JSON, plus any @2x files) from a style or tileset provider and host it under your own domain, pointing the style's sprite property at its base URL. Always check the provider's license before republishing and add attribution if required.

Host sprite assets on a static host you control (same origin as the style). Do not point production styles at `raw.githubusercontent.com`. Raw is for serving repository blobs, not production assets: anonymous requests are aggressively rate-limited so real users see intermittent HTTP 429s [5], caching is fixed at five minutes with no control, there is no SLA, and private-repo URLs return 404 to everyone but authenticated collaborators (it works for you while logged in, then fails for every other user) [6].

Building a sprite from SVGs

Generate sprite assets from a directory of SVGs with tools such as spritezero, spreet, or Martin.

Useful icon sources include Maki and Temaki. These are common source repositories for map-style SVG icons, but check each repository's license before republishing derived sprite assets.

Runtime images with addImage

For a layer your app adds at runtime, register each image under an ID once the style has loaded, then name that ID in the layer's icon-image. Check the ID with hasImage() first: addImage on an ID the style already has fires an error (An image named "<id>" already exists.) instead of replacing it.[3] [8]

js
map.on('load', async () => {
  // PNG, WebP, or JPEG: loadImage() resolves to a response whose data is the image
  const { data } = await map.loadImage('https://example.com/icons/cafe.png');
  if (!map.hasImage('cafe')) map.addImage('cafe', data);

  // SVG: an Image element, rasterized at its width and height
  const park = new Image(24, 24);
  park.crossOrigin = 'anonymous'; // or addImage throws on a cross-origin SVG
  await new Promise((resolve, reject) => {
    park.onload = resolve;
    park.onerror = reject;
    park.src = 'https://example.com/icons/park.svg';
  });
  if (!map.hasImage('park')) map.addImage('park', park);

  map.addSource('venues', { type: 'geojson', data: 'https://example.com/venues.geojson' });
  map.addLayer({
    id: 'venues',
    type: 'symbol',
    source: 'venues',
    layout: { 'icon-image': ['get', 'category'] } // 'cafe' or 'park'
  });
});
  • ❌ map.loadImage(url, callback). GL JS 4.0.0 removed the callback form,[4] so the callback never runs; await the promise.[3]
  • ❌ An SVG through map.loadImage(), which takes PNG, WebP, or JPEG.[3] To load SVGs on demand, GL JS 6's setMissingStyleImageResolver takes the same Image route, as in MapLibre's Display a remote SVG symbol example.
  • ❌ sdf: true on an ordinary SVG. An image marked sdf has its alpha read as a distance field and every opaque pixel painted icon-color (default black), so a multicolor icon turns into a one-color silhouette. Without sdf, icon-color and icon-halo-* do nothing: they only work on SDF icons.[2]
  • A symbol layer hides icons that collide by default (icon-allow-overlap is false, and icon-overlap overrides it when set); symbol-sort-key decides which survive. Turning overlap on (icon-allow-overlap: true, or icon-overlap: "always") draws every icon without checking collisions, which the GL JS large-data guide suggests at high feature counts; the icons then stack.
  • ❌ "icon-optional": true to make colliding icons disappear. It applies only to a symbol with both an icon and text, letting the text show without its icon when the icon collides and the text does not; on a layer with no text-field it changes nothing.

addImage's options take the same per-image metadata as the sprite index: pixelRatio, sdf, stretchX, stretchY, content, textFitWidth, and textFitHeight.[3]

Markers

A Marker[7] is an HTML element over the map canvas, outside the style: drawn above every layer, labels included, and never part of collision detection.

js
new maplibregl.Marker({ draggable: true })
  .setLngLat([-122.42, 37.77])
  .setPopup(new maplibregl.Popup().setText('Pickup point'))
  .addTo(map);
  • A `Marker` is one DOM element, repositioned on every camera move. Fine for a small number of annotations on top of the map; for a point dataset, put the points in a GeoJSON source and draw them as a symbol layer.

When an icon does not render

GL JS reports every icon it cannot find: the map fires styleimagemissing, and the console warns once per image ID, Image "<id>" could not be loaded.[8] A sprite URL that fails to load also fires the map's error event, which goes to console.error when nothing listens for it.[8]

After the load event, map.hasImage(id) tells the two causes apart:[3]

  • The sheet never loaded when hasImage() is false for IDs the sprite JSON does contain. Check that the style sets sprite, that its URL has no extension, that its .json and .png return 200, and CORS.
  • The sheet loaded without that ID when other sprite IDs return true. Compare the icon-image value with the sprite JSON's keys, including the id: prefix of a non-default sheet; map.listImages() returns every ID the map has.

Broken route shields

Broken-looking route shields (bare floating numbers, missing badges) are almost always a missing sprite image. The shield number is text (font) and usually renders fine; the badge behind it is an icon-image from the sprite. Diagnose in this order:

  1. Confirm glyphs load. Probe the glyphs server for the exact text-font names and expect HTTP 200. If they 200, you can deduce the culprit is a missing sprite image or broken reference.
  2. Confirm the sprite carries the shield images. OSM Bright's shield layers use icon-image: "{network}_{ref_length}" for US networks (e.g. us-interstate_2, us-highway_3, us-state_2) and road_{ref_length} for other refs; OSM Liberty's use default_{ref_length}. A missing icon is omitted, so grep the sprite JSON for those keys.

The https://demotiles.maplibre.org/styles/osm-bright-gl-style/sprite and https://openmaptiles.github.io/osm-bright-gl-style/sprite sheets currently carry road_1–_6, us-state_1–_6, us-highway_1–_3, and us-interstate_1–_3 (_1–_5 on openmaptiles.github.io), but a minimal or custom sprite may ship only the generic road_*. If yours lacks the shield images and your tiles populate network, ref, and ref_length (the OSM US OpenMapTiles tiles do), point sprite at one that has them — the {network}_{ref_length} style layers then resolve with no layer edits.

A shield is a badge image behind the route ref, sized one of two ways. OSM Bright and OSM Liberty pick a pre-drawn badge per ref length, using the OpenMapTiles ref_length attribute to select the right one.[9] OSM Bright's interstate shield layer, abridged:

json
{
  "id": "highway-shield-us-interstate",
  "layout": {
    "icon-image": "{network}_{ref_length}",
    "symbol-placement": {
      "base": 1,
      "stops": [
        [7, "point"],
        [7, "line"],
        [8, "line"]
      ]
    },
    "text-field": "{ref}"
  }
}

To use one badge image for every ref length, make it stretchable. In the badge's sprite JSON entry, stretchX and stretchY list the pixel ranges that may stretch, and content is the box the text must fit inside. On the shield layer, set icon-text-fit to "both" or "width", plus icon-text-fit-padding for a margin around the text.[1] [2] I-5 and I-405 then share one image at two widths. OSM Bright and OSM Liberty use no stretchable badges, so a tool that recognizes shield layers must accept both kinds. Only a stretchable badge sets icon-text-fit (default none), and text-anchor defaults to center, so leaving it unset says nothing about a layer.

Code that reads layout values out of a style must not assume they are strings. OSM Bright's three shield layers and OSM Liberty's one set symbol-placement with a legacy zoom function, an object with stops that switches from point to line. Any symbol layout value can also be an expression, which is an array. A check like layer.layout['symbol-placement'] === 'point' is false for those layers, so the code skips them. Check whether the value is a string, a zoom function, or an expression before comparing it.

Related Skills

  • **maplibre-cartography** — Icon color, icon-halo-color/icon-halo-width, and figure-ground for point symbols on aerial imagery; style layer ordering.
  • **maplibre-fonts-glyphs** — The glyphs URL, font PBFs, and the GL JS local-font fallback; the text half of a shield.
  • **maplibre-source-wiring** — Where sprite sits among a style's other root properties, and CORS for self-hosted assets.
  • **maplibre-v6-migration** — styleimagemissing is notify-only in v6; setMissingStyleImageResolver is how a v6 map supplies an icon-image that is not in the sheet.

References

  1. **Style Spec: `sprite`** — the string and {id, url} array forms, image-name prefixing, the default id, and the sprite index file's content, stretchX/stretchY, and textFitWidth/textFitHeight fields
  2. **Style Spec: symbol layer properties** — icon-text-fit values (none default, width, height, both) and icon-text-fit-padding; icon-color and icon-halo-* are SDF-only
  3. **`Map.addImage()` (MapLibre GL JS API)** — with hasImage(), listImages(), addSprite(), and loadImage() on the same page: loadImage takes PNG, WebP, or JPEG and resolves to a response whose data is the image
  4. **MapLibre GL JS CHANGELOG** — "Add support for multiple sprite declarations in one style file" ships in 3.0.0; map.loadImage returns a Promise and drops its callback in 4.0.0 (#3233, #3422)
  5. **Unauthenticated rate limits on `raw.githubusercontent.com` (GitHub Community Discussion)** — anonymous requests are rate-limited; production traffic sees intermittent HTTP 429
  6. **`raw.githubusercontent.com` and private repositories (GitHub Community Discussion)** — private-repo raw URLs return 404/403 to anonymous requests
  7. **`Marker` (MapLibre GL JS API)** — the draggable option, setLngLat, and setPopup
  8. **GL JS 6.11.2 `image_manager.ts`** — styleimagemissing and the missing-image warning; the duplicate-ID error is in `style.ts`, and an error event with no listener goes to console.error in `evented.ts`
  9. **OpenMapTiles `transportation_name` layer** — ref_length: "Useful for having a shield icon as background for labeling motorways."

This skill is a snapshot. Where a primary source contradicts it — the References above, MapLibre's current documentation, or what MapLibre does when you run it — that source wins. Follow it, then report the disagreement, citing the source and your MapLibre version: editing your installed copy helps no one else and is overwritten on the next update.

관련 작업

같은 저장소의 Skills

더 많은 Skills

모든 Skills
maplibre
커뮤니티

maplibre-fonts-glyphs

Configuring fonts and glyphs for MapLibre GL JS and MapLibre Native — the style's glyphs URL, self-hosting or generating font PBFs, the GL JS local-font fallback (omitted glyphs or a failed glyph fetch), MapLibre Native's font-faces property, and Noto/CJK/RTL/Devanagari script handling. Use when text labels aren't rendering in the intended font, setting up a glyphs server, choosing between self-hosting and generating font PBFs, or handling non-Latin scripts.

설치 수
2
GitHub Stars
155
업데이트
10월 18일
maplibre
커뮤니티

maplibre-pmtiles-patterns

Serverless vector and raster tiles with PMTiles for MapLibre GL JS — single-file format, HTTP range requests, hosting on S3/R2/GitHub Pages, generating with Planetiler or tippecanoe, and the pmtiles protocol. Use when you need no tile server or want to host tiles from static storage.

설치 수
2
GitHub Stars
155
업데이트
10월 18일
maplibre
커뮤니티

maplibre-source-wiring

Getting a MapLibre GL JS source to actually render — TileJSON url versus hand-wired tiles templates, matching source-layer names to the tile schema, layer order and inserting below labels, promoteId and the feature ids that feature state needs, and the CORS and glyph failures behind a blank map. Use when a source is configured but nothing is drawing, or when setFeatureState does nothing.

설치 수
2
GitHub Stars
155
업데이트
10월 18일
maplibre
커뮤니티

maplibre-terrain-rendering

Drawing elevation in MapLibre GL JS once a raster-dem source works — hillshade and hillshade-method, the color-relief layer and the ["elevation"] expression, runtime contours with maplibre-contour, and 3D terrain with its camera and sky. Use when relief looks harsh, flat, or absent, or when the layer type or property for an elevation effect is unclear.

설치 수
2
GitHub Stars
155
업데이트
10월 18일