Link

Description

This example demonstrates two things:

  • Displaying feature attributes as info cards next to the map.
  • Only showing features visible in the map viewport. Moving/zooming/filtering the map will dynamically update the list of visible features.
  • Selecting (and de-selecting) features by clicking on the list items, and keeping the selected features highlighted when the list updates.

Demo

Code

document.onreadystatechange = () => {
  if (document.readyState === "complete") {
    window.initMap("demo-viewport-filter").then(initDemo);
  }
};

function createListView(features) {
  const vectorLayer = api.map.getLayer("bag-pand-wfs-3");

  // Get selected feature id's to (re-)apply selected status to list items.
  const selectedFids = new Set(
    vectorLayer.getSelectedFeatures().features.map((f) => f.id)
  );

  if (!(features && features.length > 0)) {
    const noDataElement = document.createElement("p");
    noDataElement.innerText = "No objects in viewport";
    return noDataElement;
  }

  const list = document.createElement("ul");
  list.classList.add("data-list");

  // Order features by construction year, then by id.
  features.sort((f1, f2) => {
    if (f1.properties.bouwjaar === f2.properties.bouwjaar) {
      return f1.id.localeCompare(f2.id);
    }
    return f1.properties.bouwjaar - f2.properties.bouwjaar;
  });

  features.forEach((feature) => {
    const listItem = document.createElement("li");
    listItem.classList.add("list-item");
    const props = feature.properties;
    listItem.innerText = `Bouwjaar: ${props.bouwjaar}, Status: ${props.status}`;

    // Add feature id as data attribute to link back to the feature in the map.
    listItem.setAttribute("data-fid", feature.id);

    // Add selected class to list item if the feature has selected state.
    if (selectedFids.has(feature.id)) {
      listItem.classList.add("selected");
    }

    // Add a click handler to list items to select/deselect them
    // in the list and in the map.
    listItem.addEventListener("click", (evt) => {
      if (!(evt.target && evt.target.classList.contains("list-item"))) {
        return;
      }

      // Grab feature id from data attribute.
      const listNode = evt.target;
      const fid = listNode.dataset.fid;

      const isSelected = evt.target.classList.contains("selected");
      if (isSelected) {
        evt.target.classList.remove("selected");
        vectorLayer.unselectFeatures([fid]);
      } else {
        evt.target.classList.add("selected");
        vectorLayer.selectFeatures([fid]);
      }
    });

    list.appendChild(listItem);
  });

  return list;
}

function refreshFeatureList() {
  // Obtain current map viewport extent and use the bbox option to
  // get only the features intersecting the viewport.
  const vectorLayer = api.map.getLayer("bag-pand-wfs-3");
  const viewportExtent = api.map.getViewParams().extent;
  const featuresInViewport = vectorLayer.getFeatures({
    bbox: viewportExtent,
  }).features;

  const dataList = document.querySelector("#data-list");
  const listContainer = dataList.querySelector(".list-container");
  const featureListHtml = createListView(featuresInViewport);
  listContainer.innerHTML = "";
  listContainer.appendChild(featureListHtml);
}

function initDemo(api) {
  api.events.on("map.move", refreshFeatureList);
  api.events.on("layer.loadEnd", (evt) => {
    if (evt.layerId === "bag-pand-wfs-3") {
      refreshFeatureList();
    }
  });
}