Map-GL-Utils

repository·master·Indexed 42 days ago

https://github.com/stevage/map-gl-utils

A utility library for Mapbox GL JS and Maplibre GL JS (v0.50.4) that simplifies interactive map development. It provides syntactic sugar for layer and source management, including camelCase property support, idempotent layer operations, bulk operations via LayerRef, and high-level interaction handlers for hovering and clicking. It also includes static helpers for generating Mapbox/Maplibre expressions and a `newMap()` method for streamlined map instantiation.

Tokens
18.5K
Snippets
45
Records
59
Agent score
53%

What's inside Map-GL-Utils

  1. Specify multiple layers for operations

    master

    When using methods that act on layers (e.g., map.U.show(), map.U.hide(), map.U.toggle()), you can target one or many layers using one of the following four patterns:

    1. String: A single layer ID.
    2. Array of strings: Multiple specific layer IDs.
    3. Regular Expression: Matches layer IDs against a pattern.
    4. Filter Function: A function that receives a layer object and returns a truthy value to include it.
    map.U.show('trees-label'); // String
    map.U.show(['trees-label', 'trees-circle']); // Array
    map.U.show(/^trees-/); // Regex
    map.U.show(layer => layer.source === 'trees'); // Function
  2. Add and chain sources and layers

    master

    Methods that add sources return a SourceBoundUtils object, allowing you to chain layer additions immediately after creating a source. This provides a concise way to set up a source and its associated visual layers in a single block of code.

    map.U.addGeoJSONSource('properties')
        .addCircleLayer('properties-line', { lineWidth: 3 })
        .addSymbolLayer('properties-fill', { fillColor: 'hsla(30,30%,60%,0.5)' })
  3. How layer operations work with multiple layers

    master

    Most methods that act on existing layers (such as show(), hide(), etc.) are designed to work with a single layer or multiple layers simultaneously. You can target layers using four different selector types:

    1. String: Target a single layer ID.
    2. Array of strings: Target multiple specific layer IDs.
    3. Regular Expression: Target all layers matching a pattern.
    4. Filter Function: Target layers based on a predicate function that receives the layer object.

    This allows for powerful batch operations on groups of layers without manual iteration.

    map.U.show('trees-label'); // String
    map.U.show(['trees-label', 'trees-circle']); // Array
    map.U.show(/^trees-/); // Regex
    map.U.show(layer => layer.source === 'trees'); // Function
  4. Chain source and layer creation

    master

    Methods that add sources return a SourceBoundUtils object, allowing you to chain layer additions immediately after creating a source. This provides a concise way to set up a source and its associated visual layers in a single statement.

    map.U.addGeoJSONSource('properties')
    .addCircleLayer('properties-line', { lineWidth: 3 })
    .addSymbolLayer('properties-fill', { fillColor: 'hsla(30,30%,60%,0.5)' })
  5. How Map-GL-Utils simplifies layer and property management

    master

    Map-GL-Utils provides a more concise API for interacting with map layers and sources by applying several abstractions:

    • Unified Property Management: You no longer need to distinguish between paint, layout, and other property types. All properties can be passed in a single object.
    • CamelCase Support: Property keys can be written in camelCase (e.g., lineWidth) instead of the standard Mapbox/Maplibre kebab-case (line-width).
    • Type-Specific Methods: Function names incorporate the source or layer type, such as addGeoJSON(), addCircleLayer(), or setCircleRadius().
    • Idempotent Operations: Adding a layer (e.g., addLineLayer()) is idempotent; calling it multiple times will create the layer if it doesn't exist or update it if it does.
    • Bulk Operations: Most layer methods (like show(), hide(), or setFilter()) can act on multiple layers simultaneously using strings, arrays, regular expressions, or filter functions.
    • Enhanced Interaction: Provides specialized functions for common tasks like hoverPointer(), hoverFeatureState(), hoverPopup(), and clickLayer().
  6. Install and initialize Map-GL-Utils

    master

    Map-GL-Utils can be used with Mapbox GL JS or Maplibre GL JS. You must initialize the utility on your map instance using U.init(map).

    Using via CDN (No build process)

    Include the script tag in your HTML:

    <script src="https://unpkg.com/map-gl-utils"></script>

    Then initialize in your JavaScript:

    U.init(map);

    Using with Webpack/Bundlers

    Install via npm and import the module. If you need the ESM version explicitly, import from the dist path.

    Standard ESM Import:

    import mapgl from 'maplibre-gl'; // or 'mapbox-gl'
    import U from 'map-gl-utils';
    const map = new mapgl.Map({ ... });
    U.init(map);

    Explicit ESM Import:

    import U from 'map-gl-utils/dist/index.esm.js';

    UMD Bundle (for older environments): If you encounter syntax issues with the default ES2015 module, use the UMD bundle which adds a U property directly to the map instance.

    require('map-gl-utils/umd').init(map);

    Note on Library Access: Some methods (like hoverPopup) require access to the underlying map library (Mapbox or Maplibre) to instantiate objects. In these cases, pass the library instance to init:

    require('map-gl-utils').init(map, mapgl);
    import mapgl from 'maplibre-gl';
    import U from 'map-gl-utils';
    const map = new mapgl.Map({ ... });
    U.init(map);
  7. How LayerRef and SourceRef work

    master

    Many utility functions accept a LayerRef or SourceRef instead of a single string ID. This allows you to target multiple layers at once using several formats:

    • String: A single layer ID.
    • Array of Strings: Multiple specific layer IDs.
    • RegExp: A regular expression that matches layer IDs.
    • Function: A predicate function that receives a LayerSpecification and returns true if the layer should be included.

    This abstraction is used to "magically" turn functions that work on one layer into functions that work on all matching layers.

  8. Initialize Map-GL-Utils on an existing map

    master

    To use the utility functions on a Mapbox GL JS or Maplibre GL JS map instance, you must first attach the UtilsMap interface to it using MapGlUtils.init(). This adds a .U property to your map object containing all the utility methods.

    Note: If you are using Maplibre GL JS, you must pass the library object as the second argument to init to enable features like popups.

    // For Mapbox GL JS
    MapGlUtils.init(map);
    
    // For Maplibre GL JS
    MapGlUtils.init(map, maplibregl);
  9. Add a circle layer with mixed properties

    master

    When adding a layer using addCircleLayer(), you can provide a single props object that mixes paint, layout, and other properties. You can use either camelCase or kebab-case for property keys. The utility handles the distinction between property types automatically.

    map.U.addCircleLayer('trees-circle', 'trees', {
        circleColor: 'green', // paint property
        circleRadius: ['interpolate', ['zoom'], 12, 3, 15, 5], // paint property
        circleSortKey: ['get', 'tree-sort-key'], // layout property
        filter: ['!=', 'type', 'stump'], // other property
    });
  10. Add a layer with mixed properties

    master

    When adding a layer using add[Type]Layer(), you can pass a props object that mixes paint, layout, and other properties. You can use camelCase for all keys.

    map.U.addCircleLayer('trees-circle', 'trees', {
        circleColor: 'green', // paint property
        circleRadius: ['interpolate', ['zoom'], 12, 3, 15, 5], // paint property
        circleSortKey: ['get', 'tree-sort-key'], // layout property
        filter: ['!=', 'type', 'stump'], // other property
    });
  11. Use other utility functions

    master

    Additional utilities for map management:

    • Lifecycle: onLoad(callback) fires when the map is loaded. It returns a Promise if no callback is provided.
    • Styling: getLayerStyle(id) returns a cleaned-up layer definition. setLayerStyle(id, properties) resets properties to defaults before applying new ones. layerStyle(...) is a flexible constructor for creating layer objects.
    • Source Visibility: hideSource(id), showSource(id), and toggleSource(id, bool) manage all layers attached to a source.
    • Assets: loadImage(id, url, options) loads an image into the map.
    • Fonts: fontsInUse() returns an array of fonts currently used in symbol layers.
    // Wait for map load
    await map.U.onLoad();
    
    // Get cleaned layer style
    const style = map.U.getLayerStyle('mylayer');
    
    // Reset and set layer style
    map.U.setLayerStyle('mylayer', { lineWidth: 3 });
    
    // Load an image
    map.U.loadImage('marker', '/assets/marker-pin.png').then(() => {
        console.log('Loaded');
    });
    
    // Get fonts for symbol layers
    const fonts = map.U.fontsInUse();
  12. Handle hovering and clicking

    master

    The library simplifies interactive map events:

    • hoverPointer(layers): Changes cursor to a 'finger' when hovering over specified layers.
    • hoverFeatureState(layer): Manages feature-state for hover effects (requires features to have an id).
    • hoverPopup(layer, callback, options): Shows a popup when hovering. The callback receives (feature, popup) and returns an HTML string. Requires U.init(map, mapboxgl) to be called first.
    • clickLayer(layers, callback): Adds a click listener that includes a features array in the event object.
    • clickOneLayer(layers, callback, missCallback): Tests multiple layers in order and fires the callback for the first hit. An optional missCallback runs if no layers are hit.

    Note: All event handler functions return an 'undo' function that, when called, removes the handler.

    // Cursor and Feature State
    map.U.hoverPointer(['regions-border', 'regions-fill']);
    map.U.hoverFeatureState('mylayer');
    
    // Popups (Requires U.init(map, mapboxgl))
    const remove = map.U.hoverPopup('mylayer', f => `<h3>${f.properties.Name}</h3>`, { anchor: 'left' });
    remove(); // Removes the hover popup
    
    // Clicking
    map.U.clickLayer(['towns', 'town-labels'], e => {
        console.log(e.features[0].id);
    });
    
    map.U.clickOneLayer(['town-labels', 'state-boundaries'], e => {
        if (e.layer === 'town-labels') { /* ... */ }
    }, e => {
        console.log('Missed everything');
    });