stipple-maplibre 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -66,7 +66,7 @@ and provides the corresponding `fill-pattern` value.
66
66
  </td>
67
67
  <td align="center" width="25%">
68
68
  <img src="./assets/readme/example-custom-svg.png" alt="Polygon filled with a custom SVG motif" width="100%"><br>
69
- <sub><em>Or even your custom SVG</em></sub>
69
+ <sub><em>Custom</em></sub>
70
70
  </td>
71
71
  </tr>
72
72
  </table>
@@ -227,12 +227,30 @@ The playground can export:
227
227
 
228
228
  - ready-to-use MapLibre code;
229
229
  - a reusable Stipple configuration;
230
- - a MapLibre style document;
231
- - a bundle containing the generated pattern images.
230
+ - a static bundle containing style layers, generated pattern images, and an
231
+ integration helper.
232
232
 
233
- For code-driven styles, `buildStyleFragment` creates the background, pattern,
234
- and outline layers together. The resulting style metadata carries the pattern
235
- recipe. After the style loads, `installPatternFills` reads those recipes and
233
+ The copied code uses `addPatternFill`, which waits for the map style, installs
234
+ the generated textures, adapts the exported layers to an existing source, and
235
+ adds them in order:
236
+
237
+ ```js
238
+ import { addPatternFill } from "stipple-maplibre";
239
+
240
+ await addPatternFill(map, {
241
+ sourceId: "my-polygons",
242
+ sourceLayer: null, // Use the source-layer name for vector tiles.
243
+ pattern: exportedPattern,
244
+ });
245
+ ```
246
+
247
+ The source must already exist in the map. An optional `beforeId` places the
248
+ pattern layers below an existing label or symbol layer. The returned
249
+ `layerIds` list contains every layer added to the map.
250
+
251
+ For lower-level, code-driven styles, `buildStyleFragment` creates the
252
+ background, pattern, and outline layers together. The resulting style metadata
253
+ carries the pattern recipe. `installPatternFills` reads those recipes and
236
254
  installs every texture the map needs:
237
255
 
238
256
  ```js
@@ -321,7 +339,7 @@ in Node.
321
339
 
322
340
  ## Project status
323
341
 
324
- Version `0.2.0` is available on
342
+ Version `0.3.0` is available on
325
343
  [npm](https://www.npmjs.com/package/stipple-maplibre). The whole-symbol
326
344
  scatter API is the only part currently marked experimental.
327
345
 
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Map } from 'maplibre-gl';
1
+ import { Map, LayerSpecification } from 'maplibre-gl';
2
2
 
3
3
  type PatternType = "solid" | "stipple" | "hachures" | "cross" | "grid" | "dots";
4
4
  /** Tile edge length in px. MapLibre does not require power-of-two fill-pattern images. Larger tiles read as a lower-density pattern. */
@@ -396,6 +396,37 @@ interface StyleLike {
396
396
  */
397
397
  declare function installPatternFills(map: Map, style: StyleLike): Promise<void>;
398
398
 
399
+ interface PatternFillLayerTemplate {
400
+ id: string;
401
+ type: LayerSpecification["type"];
402
+ metadata?: Record<string, unknown>;
403
+ [key: string]: unknown;
404
+ }
405
+ interface PatternFillFragment {
406
+ layers: PatternFillLayerTemplate[];
407
+ }
408
+ interface AddPatternFillOptions {
409
+ /** Id of a source that already exists in the map. */
410
+ sourceId: string;
411
+ /** Omit or use null for GeoJSON. Required for vector tile sources. */
412
+ sourceLayer?: string | null;
413
+ /** Insert every generated layer immediately before this existing layer. */
414
+ beforeId?: string;
415
+ /** Source-neutral layer templates exported by Stipple. */
416
+ pattern: PatternFillFragment;
417
+ }
418
+ interface AddedPatternFill {
419
+ layerIds: string[];
420
+ }
421
+ /**
422
+ * Installs a Stipple pattern export on an existing MapLibre source.
423
+ *
424
+ * The helper waits for the style, resolves the source placeholders, installs
425
+ * every generated texture, then adds the exported background, fill, and
426
+ * outline layers in order.
427
+ */
428
+ declare function addPatternFill(map: Map, options: AddPatternFillOptions): Promise<AddedPatternFill>;
429
+
399
430
  interface ObservePatternFillsOptions {
400
431
  /** Defaults to `map.getStyle()`. Useful when metadata is stored separately. */
401
432
  getStyle?: () => StyleLike;
@@ -602,4 +633,4 @@ interface InstallSvgIconScatterOptions {
602
633
  */
603
634
  declare function installSvgIconScatter(map: Map, options: InstallSvgIconScatterOptions): Promise<void>;
604
635
 
605
- export { type BackgroundFillConfig, type BuildStyleFragmentOptions, type FontPatternDefinition, type FontPatternOptions, type GeoJsonPolygonImport, type GeometricPatternDefinition, type GeometricPatternType, type HachureAngle, type IconScaleMode, type ImportedPolygonFeature, type InstallSvgIconScatterOptions, type MakeTileOptions, type ObservePatternFillsOptions, type OutlineConfig, PATTERN_METADATA_KEY, type PatternDefinition, type PatternFillConfig, type PatternFillObserver, type PatternMetadataV1, type PatternScaleMode, type PatternScaleOptions, type PatternScaleResult, type PatternType, type PatternVariant, type PointFeature, type PointFeatureCollection, type PolygonGeometry, type Ring, type ScatterIconPointsOptions, type ScatterPointsOptions, type ScatteredPoint, type ScreenPatternPhase, type StyleLike, type SvgDistributionMode, type SvgPatternDefinition, type SvgPatternOptions, type SvgScatterLayoutOptions, type SvgStampPlacement, type SyncPatternTextureOptions, type TileContext, type TileImage, type TileSize, buildStyleFragment, createFontPatternDefinition, createFontPatternTile, createMiniContext, createSvgPatternDefinition, createSvgScatterLayout, createSvgScatterTile, hashStringToSeed, importGeoJsonPolygons, installFontPatternFill, installPatternFills, installSvgIconScatter, installSvgPatternFill, makeTile, mulberry32, observePatternFills, parsePatternDefinition, parsePatternMetadata, patternDefinitionId, scalePatternForZoom, scatterIconPoints, scatterPointsInPolygon, screenPatternPhase, serializePatternDefinition, syncPatternTexture };
636
+ export { type AddPatternFillOptions, type AddedPatternFill, type BackgroundFillConfig, type BuildStyleFragmentOptions, type FontPatternDefinition, type FontPatternOptions, type GeoJsonPolygonImport, type GeometricPatternDefinition, type GeometricPatternType, type HachureAngle, type IconScaleMode, type ImportedPolygonFeature, type InstallSvgIconScatterOptions, type MakeTileOptions, type ObservePatternFillsOptions, type OutlineConfig, PATTERN_METADATA_KEY, type PatternDefinition, type PatternFillConfig, type PatternFillFragment, type PatternFillLayerTemplate, type PatternFillObserver, type PatternMetadataV1, type PatternScaleMode, type PatternScaleOptions, type PatternScaleResult, type PatternType, type PatternVariant, type PointFeature, type PointFeatureCollection, type PolygonGeometry, type Ring, type ScatterIconPointsOptions, type ScatterPointsOptions, type ScatteredPoint, type ScreenPatternPhase, type StyleLike, type SvgDistributionMode, type SvgPatternDefinition, type SvgPatternOptions, type SvgScatterLayoutOptions, type SvgStampPlacement, type SyncPatternTextureOptions, type TileContext, type TileImage, type TileSize, addPatternFill, buildStyleFragment, createFontPatternDefinition, createFontPatternTile, createMiniContext, createSvgPatternDefinition, createSvgScatterLayout, createSvgScatterTile, hashStringToSeed, importGeoJsonPolygons, installFontPatternFill, installPatternFills, installSvgIconScatter, installSvgPatternFill, makeTile, mulberry32, observePatternFills, parsePatternDefinition, parsePatternMetadata, patternDefinitionId, scalePatternForZoom, scatterIconPoints, scatterPointsInPolygon, screenPatternPhase, serializePatternDefinition, syncPatternTexture };
@@ -22,6 +22,7 @@ var MaplibrePatternFills = (() => {
22
22
  var src_exports = {};
23
23
  __export(src_exports, {
24
24
  PATTERN_METADATA_KEY: () => PATTERN_METADATA_KEY,
25
+ addPatternFill: () => addPatternFill,
25
26
  buildStyleFragment: () => buildStyleFragment,
26
27
  createFontPatternDefinition: () => createFontPatternDefinition,
27
28
  createFontPatternTile: () => createFontPatternTile,
@@ -1480,6 +1481,83 @@ var MaplibrePatternFills = (() => {
1480
1481
  await Promise.all(pending);
1481
1482
  }
1482
1483
 
1484
+ // src/maplibre/addPatternFill.ts
1485
+ function nonEmptyString2(value, name) {
1486
+ if (typeof value !== "string" || value.trim().length === 0) {
1487
+ throw new TypeError(`${name} must be a non-empty string`);
1488
+ }
1489
+ }
1490
+ async function waitForStyle(map) {
1491
+ if (map.getStyle()) return;
1492
+ await new Promise((resolve) => {
1493
+ map.once("style.load", () => resolve());
1494
+ });
1495
+ }
1496
+ async function addPatternFill(map, options) {
1497
+ nonEmptyString2(options.sourceId, "sourceId");
1498
+ if (options.sourceLayer !== void 0 && options.sourceLayer !== null) {
1499
+ nonEmptyString2(options.sourceLayer, "sourceLayer");
1500
+ }
1501
+ if (options.beforeId !== void 0) {
1502
+ nonEmptyString2(options.beforeId, "beforeId");
1503
+ }
1504
+ if (!options.pattern || !Array.isArray(options.pattern.layers)) {
1505
+ throw new TypeError("pattern.layers must be an array");
1506
+ }
1507
+ if (options.pattern.layers.length === 0) {
1508
+ throw new RangeError("pattern.layers must contain at least one layer");
1509
+ }
1510
+ await waitForStyle(map);
1511
+ if (!map.getSource(options.sourceId)) {
1512
+ throw new Error(`MapLibre source "${options.sourceId}" was not found`);
1513
+ }
1514
+ if (options.beforeId && !map.getLayer(options.beforeId)) {
1515
+ throw new Error(`MapLibre layer "${options.beforeId}" was not found`);
1516
+ }
1517
+ const layers = options.pattern.layers.map((template, index) => {
1518
+ if (!template || typeof template !== "object") {
1519
+ throw new TypeError(`pattern.layers[${index}] must be an object`);
1520
+ }
1521
+ nonEmptyString2(template.id, `pattern.layers[${index}].id`);
1522
+ nonEmptyString2(template.type, `pattern.layers[${index}].type`);
1523
+ const layer = {
1524
+ ...template,
1525
+ id: template.id.split("<source>").join(options.sourceId),
1526
+ source: options.sourceId
1527
+ };
1528
+ if (options.sourceLayer === void 0 || options.sourceLayer === null) {
1529
+ delete layer["source-layer"];
1530
+ } else {
1531
+ layer["source-layer"] = options.sourceLayer;
1532
+ }
1533
+ return layer;
1534
+ });
1535
+ const layerIds = layers.map((layer) => layer.id);
1536
+ const uniqueIds = new Set(layerIds);
1537
+ if (uniqueIds.size !== layerIds.length) {
1538
+ throw new Error("Pattern fill contains duplicate layer ids");
1539
+ }
1540
+ for (const layerId of layerIds) {
1541
+ if (map.getLayer(layerId)) {
1542
+ throw new Error(`MapLibre layer "${layerId}" already exists`);
1543
+ }
1544
+ }
1545
+ await installPatternFills(map, { layers });
1546
+ const addedLayerIds = [];
1547
+ try {
1548
+ for (const layer of layers) {
1549
+ map.addLayer(layer, options.beforeId);
1550
+ addedLayerIds.push(layer.id);
1551
+ }
1552
+ } catch (error) {
1553
+ for (const layerId of addedLayerIds.reverse()) {
1554
+ if (map.getLayer(layerId)) map.removeLayer(layerId);
1555
+ }
1556
+ throw error;
1557
+ }
1558
+ return { layerIds };
1559
+ }
1560
+
1483
1561
  // src/maplibre/observePatternFills.ts
1484
1562
  function observePatternFills(map, options = {}) {
1485
1563
  const getStyle = options.getStyle ?? (() => map.getStyle());
package/dist/index.js CHANGED
@@ -1429,6 +1429,83 @@ async function installPatternFills(map, style) {
1429
1429
  await Promise.all(pending);
1430
1430
  }
1431
1431
 
1432
+ // src/maplibre/addPatternFill.ts
1433
+ function nonEmptyString2(value, name) {
1434
+ if (typeof value !== "string" || value.trim().length === 0) {
1435
+ throw new TypeError(`${name} must be a non-empty string`);
1436
+ }
1437
+ }
1438
+ async function waitForStyle(map) {
1439
+ if (map.getStyle()) return;
1440
+ await new Promise((resolve) => {
1441
+ map.once("style.load", () => resolve());
1442
+ });
1443
+ }
1444
+ async function addPatternFill(map, options) {
1445
+ nonEmptyString2(options.sourceId, "sourceId");
1446
+ if (options.sourceLayer !== void 0 && options.sourceLayer !== null) {
1447
+ nonEmptyString2(options.sourceLayer, "sourceLayer");
1448
+ }
1449
+ if (options.beforeId !== void 0) {
1450
+ nonEmptyString2(options.beforeId, "beforeId");
1451
+ }
1452
+ if (!options.pattern || !Array.isArray(options.pattern.layers)) {
1453
+ throw new TypeError("pattern.layers must be an array");
1454
+ }
1455
+ if (options.pattern.layers.length === 0) {
1456
+ throw new RangeError("pattern.layers must contain at least one layer");
1457
+ }
1458
+ await waitForStyle(map);
1459
+ if (!map.getSource(options.sourceId)) {
1460
+ throw new Error(`MapLibre source "${options.sourceId}" was not found`);
1461
+ }
1462
+ if (options.beforeId && !map.getLayer(options.beforeId)) {
1463
+ throw new Error(`MapLibre layer "${options.beforeId}" was not found`);
1464
+ }
1465
+ const layers = options.pattern.layers.map((template, index) => {
1466
+ if (!template || typeof template !== "object") {
1467
+ throw new TypeError(`pattern.layers[${index}] must be an object`);
1468
+ }
1469
+ nonEmptyString2(template.id, `pattern.layers[${index}].id`);
1470
+ nonEmptyString2(template.type, `pattern.layers[${index}].type`);
1471
+ const layer = {
1472
+ ...template,
1473
+ id: template.id.split("<source>").join(options.sourceId),
1474
+ source: options.sourceId
1475
+ };
1476
+ if (options.sourceLayer === void 0 || options.sourceLayer === null) {
1477
+ delete layer["source-layer"];
1478
+ } else {
1479
+ layer["source-layer"] = options.sourceLayer;
1480
+ }
1481
+ return layer;
1482
+ });
1483
+ const layerIds = layers.map((layer) => layer.id);
1484
+ const uniqueIds = new Set(layerIds);
1485
+ if (uniqueIds.size !== layerIds.length) {
1486
+ throw new Error("Pattern fill contains duplicate layer ids");
1487
+ }
1488
+ for (const layerId of layerIds) {
1489
+ if (map.getLayer(layerId)) {
1490
+ throw new Error(`MapLibre layer "${layerId}" already exists`);
1491
+ }
1492
+ }
1493
+ await installPatternFills(map, { layers });
1494
+ const addedLayerIds = [];
1495
+ try {
1496
+ for (const layer of layers) {
1497
+ map.addLayer(layer, options.beforeId);
1498
+ addedLayerIds.push(layer.id);
1499
+ }
1500
+ } catch (error) {
1501
+ for (const layerId of addedLayerIds.reverse()) {
1502
+ if (map.getLayer(layerId)) map.removeLayer(layerId);
1503
+ }
1504
+ throw error;
1505
+ }
1506
+ return { layerIds };
1507
+ }
1508
+
1432
1509
  // src/maplibre/observePatternFills.ts
1433
1510
  function observePatternFills(map, options = {}) {
1434
1511
  const getStyle = options.getStyle ?? (() => map.getStyle());
@@ -1734,6 +1811,7 @@ async function installSvgIconScatter(map, options) {
1734
1811
  }
1735
1812
  export {
1736
1813
  PATTERN_METADATA_KEY,
1814
+ addPatternFill,
1737
1815
  buildStyleFragment,
1738
1816
  createFontPatternDefinition,
1739
1817
  createFontPatternTile,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stipple-maplibre",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Runtime-generated geometric and SVG fill patterns for MapLibre GL JS, with deterministic styling and a visual playground.",
5
5
  "license": "MIT",
6
6
  "type": "module",