jscastro76 / threebox

A Three.js plugin for Mapbox GL JS, with support for animations and advanced 3D rendering.
Other
562 stars 148 forks source link

Issue when removing tooltip layer, feature is not found by mapbox #354

Closed simtrax closed 2 years ago

simtrax commented 2 years ago

Describe the bug I'm trying to dynamically add and remove tooltips when the user interacts with features on the map.

To Reproduce Steps to reproduce the behavior: View the HTML file with python http server or something similar.

  1. Click somewhere on the top of the feature on the map.
  2. In the console you will see two features being registered, the 3D feature and the map.
  3. Click "Remove tooltip" button in top left corner.
  4. Repeat step 1 and 2, now the output is only the the map, not the 3D layer.

If clicking on the base of the 3D feature it will be registered

Expected behavior After the layer has been removed the 3D feature should still be found by the map when querying features. But it seems that the 3D information is lost.

For me it was easiest to create a HTML file, sorry if it's inconvenient.

<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>Add a 3D model</title>
    <meta
      name="viewport"
      content="initial-scale=1,maximum-scale=1,user-scalable=no"
    />
    <link
      href="https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.css"
      rel="stylesheet"
    />
    <script src="https://api.mapbox.com/mapbox-gl-js/v2.8.2/mapbox-gl.js"></script>

    <script
      src="https://cdn.jsdelivr.net/gh/jscastro76/threebox@v.2.2.2/dist/threebox.min.js"
      type="text/javascript"
    ></script>
    <link
      href="https://cdn.jsdelivr.net/gh/jscastro76/threebox@v.2.2.2/dist/threebox.css"
      rel="stylesheet"
    />
  </head>
  <body>
    <script src="https://unpkg.com/three@0.126.0/build/three.min.js"></script>
    <script src="https://unpkg.com/three@0.126.0/examples/js/loaders/GLTFLoader.js"></script>
    <div id="map" style="position: relative; height: 100vh; width: 100%"></div>
    <div id="button-div" style="position: absolute; top: 10px; left: 10px">
      <button>Remove tooltip</button>
    </div>
    <script type="module">
      var featureCollection = {
        type: "FeatureCollection",
        features: [
          {
            type: "Feature",
            properties: {
              height: 50,
            },
            geometry: {
              type: "Polygon",
              coordinates: [
                [
                  [13.267155665376963, 55.63285120076501],
                  [13.267159630022254, 55.63276138703882],
                  [13.267318393601501, 55.63276362975903],
                  [13.267314429319025, 55.63285344349271],
                  [13.267155665376963, 55.63285120076501],
                ],
              ],
            },
          },
        ],
      };

      const button = document.querySelector("button");
      button.addEventListener("click", (event) => {
        map.removeLayer("custom_layer");
        window.tb.clear();
      });

      mapboxgl.accessToken =
        "mapbox token";
      const map = new mapboxgl.Map({
        container: "map",
        style: "mapbox://styles/mapbox/outdoors-v11",
        zoom: 16,
        center: [13.262164, 55.630104],
        pitch: 45,
        antialias: true, // create the gl context with MSAA antialiasing, so custom layers are antialiased
        hash: true,
      });

      window.tb = new Threebox(map, map.getCanvas().getContext("webgl"), {
        // defaultLights: true,
        // enableSelectingFeatures: true, //change this to false to disable fill-extrusion features selection
        // enableTooltips: true, // change this to false to disable default tooltips on fill-extrusion and 3D models
      });

      map.on("click", (e) => {
        var features = map.queryRenderedFeatures(e.point);
        console.log("features", features);

        addLabel(features[0]);
      });

      map.on("style.load", () => {
        map.addSource("features", {
          type: "geojson",
          data: featureCollection,
        });

        map.addLayer({
          id: "yieldPrediction",
          type: "fill",
          source: "features",
          type: "fill-extrusion",
          paint: {
            "fill-extrusion-color": "red",
            "fill-extrusion-height": ["get", "height"],
            "fill-extrusion-base": 0,
            "fill-extrusion-opacity": 1,
          },
        });
      });

      function addLabel(feature) {
        map.addLayer({
          id: "custom_layer",
          type: "custom",
          renderingMode: "3d",
          onAdd: (map, mbxContext) => {
            let label = tb.label({
              position: [13.267155960202217, 55.63285118597588, 50],
              htmlElement: createLabel(),
              cssClass: " label3D",
              alwaysVisible: true,
              bottomMargin: 0,
              feature: null,
            });

            label.setCoords([13.267155960202217, 55.63285118597588, 50]);
            tb.add(label);
          },

          render: (gl, matrix) => {
            tb.update();
          },
        });
      }

      function createLabel() {
        let divToolTip;
        let divContent = document.createElement("div");
        divContent.className = "mapboxgl-popup-content";
        divContent.style.width = "fit-content";
        let strong = document.createElement("strong");
        strong.innerHTML = "Ost";
        divContent.appendChild(strong);
        let tip = document.createElement("div");
        tip.className = "mapboxgl-popup-tip";
        let div = document.createElement("div");
        div.className = "marker mapboxgl-popup-anchor-bottom";
        div.appendChild(tip);
        div.appendChild(divContent);
        divToolTip = document.createElement("div");
        divToolTip.className += "label3D";
        divToolTip.appendChild(div);
        return divToolTip;
      }
    </script>
  </body>
</html>

Console Results


features first click 
0: Yp {type: 'Feature', _vectorTileFeature: Iu, _z: 16, _x: 35183, _y: 20526, …}
1: Yp {type: 'Feature', _vectorTileFeature: Iu, _z: 16, _x: 35183, _y: 20526, …}
length: 2
[[Prototype]]: Array(0)

features second click after remove tooltip was clicked
0: Yp {type: 'Feature', _vectorTileFeature: Iu, _z: 16, _x: 35183, _y: 20526, …}
length: 1
[[Prototype]]: Array(0)

- [Version 2.2.2
jscastro76 commented 2 years ago

Hi @simtrax Labels and tooltips are constantly added and removed in each one of the 22 examples, so this is not a bug in threebox. As you don't provide a code sandbox to test your code, my first feeling is that you are doing wrong assumptions or decisions in your code that are producing that error. To show labels in a fill extrusion layer, you don't need to build the labels manually, as you can see it in this example

First of all, you're assuming every click on the map will return features, that's mostly wrong, it could return an empty array. Second you create and remove the 3D layer every time, which is really a bad practice, extremely inefficient, and can produce other colateral errors. Other things I don't understand in your code is that you send the feature to addlabel method but don't use it. You're also using Mapbox v2.8.2, which this plugin hasn't been tested to (it could work or not), but hasn't been tested.

simtrax commented 2 years ago

Thank you for the response @jscastro76

First, sorry for not sending a sandbox. This is just a very quick mock-up of what I wanted to achieve. I do not want to remove the whole layer, I understand that is bad practice. I have tried with different versions of Mabbox GL JS, does not seem to matter. I saw that I hade included Three.js, that was my mistake, I was tired after several hours of trying to understand why I could not remove tooltips from the map without it breaking things.

I have looked through many of the examples in the repository, especially 08-3dbuildings.html, but as far as I have seen all the tooltips are shown when hovering over a feature. I want to display a tooltip when the user clicks on a feature. I then want to let the user click on another feature and have two tooltips be displayed at the same time. Somewhere else there's a "Clear selection" button, that should remove the tooltips. After that the user should be able to place new tooltips if they want to. And I want to be in control of what is displayed in the tooltip. It might be different for each feature the user clicks on.

I'm not asking you to send code how to do this, I'm just interested in knowing if it is possible. And if possible, is there a example that states how to remove manually added tooltips anywhere? Without it breaking how features added to the map behaves.

I just want tooltips shown on top of an extruded features, that's the only thing I need. But I want to control it manually. This repo might be overkill just to do that, but this is the only way I have found to make it work.

It might not be of any help but here's a sandbox. Click me

jscastro76 commented 2 years ago

I added my token to your code sandbox, and I'm receiving other errors every time I click on the red building. image

simtrax commented 2 years ago

@jscastro76 Let's make it a bit simpler, is there a way to manually add and remove tooltips that are placed at an altitude? If there is, what is the correct way to do it? Thanks!

jscastro76 commented 2 years ago

The best way to do it for a Fill extrusion feature is to basically call the method map.addTooltip that receives a feature and has been created specifically for fill-extrusions. But as it seems you want a specific behavior, you can create a custom tooltip programming your own method based on its code, that creates a tb.tooltip. You can avoid the feature and using just coordinates array including altitude [long, lat, altitude]. Just be clear then you will need to manage the visibility and removal of that object separately.

You have everything in the documentation of tb.addTolltip tb.tooltip