Category filtering using metadata

Estimated reading time: 2 minutes

Use model metadata to create category filtering.

See the manual for setup information.

View on GitHub

<!doctype html>
<html>
  <head>
    <meta charset="UTF-8" />
    <link href="https://unpkg.com/[email protected]/dist/maplibre-gl.css" rel="stylesheet" />
    <script src="https://unpkg.com/[email protected]/dist/maplibre-gl.js"></script>
    <style>
      html,
      body {
        margin: 0;
        padding: 0;
        height: 100%;
      }

      #map-container {
        height: 100%;
        position: relative;
      }

      #map {
        min-height: 500px;
        height: 100%;
      }

      #menu {
        font:
          12px/20px "Helvetica Neue",
          Arial,
          Helvetica,
          sans-serif;
        position: absolute;
        width: 175px;
        top: 0;
        left: 0;
        padding: 10px;
        background-color: #fff;
        box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
        border-radius: 3px;
        padding: 10px;
        margin: 10px 10px;
      }

      #menu input[type="radio"] {
        margin: 3px 3px 0px 5px;
      }

      #menu label {
        display: block;
        font-weight: bold;
        margin: 0 0 5px;
      }

      .category-filter {
        text-transform: capitalize;
      }
    </style>
  </head>

  <body>
    <div id="map-container">
      <div id="map"></div>
      <div id="menu"></div>
    </div>
    <script>
      (async () => {
        // The property name containing the model id. This property is set by Jawg GIS during import.
        const JAWG_MODEL_PROPERTY_NAME = "jawg:model";

        // Don't forget to replace <YOUR_GIS_ACCESS_TOKEN> by your own access token
        const accessToken = "<YOUR_GIS_ACCESS_TOKEN>";
        const mapId = "<YOUR_GIS_MAP_ID>";
        const map = new maplibregl.Map({
          container: "map",
          style: `https://api.jawg.io/gis/maps/${mapId}/versions/latest/style?access-token=${accessToken}`,
          zoom: 12,
          center: [2.35071554, 48.85826376],
        });
        const useNavigationControl = new URLSearchParams(window.location.search).get("navigation-control");
        if (useNavigationControl !== "false") {
          map.addControl(new maplibregl.NavigationControl(), "top-right");
        }
        // This plugin is used for right to left languages
        maplibregl.setRTLTextPlugin("https://unpkg.com/@mapbox/[email protected]/mapbox-gl-rtl-text.min.js");

        const metadata = await fetch(
          `https://api.jawg.io/gis/maps/${mapId}/versions/latest/metadata?access-token=${accessToken}`,
        ).then((resp) => resp.json());

        // Group model ids by category
        const categories = metadata.datasets
          .flatMap((dataset) => dataset.models)
          .filter((model) => model.metadata.hasOwnProperty("category"))
          .reduce((cat, model) => {
            (cat[model.metadata["category"]] = cat[model.metadata["category"]] || []).push(model.id);
            return cat;
          }, {});

        // Create the category filter elements
        // This is the name of the style layer displaying the points as symbols. Find the layer name in the GIS https://www.jawg.io/gis/styles
        const layerName = "paris-places";
        const categoryFiltersElement = document.getElementById("menu");
        Object.entries(categories).forEach((category) => {
          const [categoryName, modelIds] = category;
          const input = document.createElement("input");
          input.setAttribute("id", categoryName);
          input.setAttribute("type", "radio");
          input.setAttribute("name", "category-filter");
          input.setAttribute("value", categoryName);
          // On click we filter points with an "in" expression. See https://maplibre.org/maplibre-gl-js-docs/style-spec/expressions/#in for more informations
          const filterExpr = ["in", ["get", JAWG_MODEL_PROPERTY_NAME], ["literal", modelIds]];
          input.onclick = () => map.setFilter(layerName, filterExpr);
          const label = document.createElement("label");
          label.setAttribute("class", "category-filter");
          label.appendChild(input);
          label.appendChild(document.createTextNode(categoryName));
          categoryFiltersElement.appendChild(label);
        });
      })();
    </script>
  </body>
</html>