demo: cesium map add VectorTileServer layer

815
1
11-22-2023 01:24 AM
Status: Open
Labels (1)
liqiuping
New Contributor II

i recently made it as to add VectorTileServer layer in a cesium map. 

hope this could be helpful for anyone.

 

 

<html>
  <head>
    <meta charset="utf-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
    />
    <title>Cesium + Webpack</title>
    <style>
      body,
      #cesiumContainer {
        width: 100%;
        height: 100%;
        margin: 0;
      }
      .toolbar-item {
        display: inline-block;
        margin-right: 10px;
      }
    </style>

   <link
      href="https://cesium.com/downloads/cesiumjs/releases/1.111/Build/Cesium/Widgets/widgets.css"
      rel="stylesheet"
    />
    <script src="https://cesium.com/downloads/cesiumjs/releases/1.111/Build/Cesium/Cesium.js"></script>
  
    <script src="https://github.com/landtechnologies/Mapbox-vector-tiles-basic-js-renderer/blob/master/dist/mapbox-gl.js"></script> -->
    <link href="https://github.com/landtechnologies/Mapbox-vector-tiles-basic-js-renderer/blob/master/dist/mapbox-gl.css" rel="stylesheet" />

    <script>
      class MVTImageryProvider {
        /**
         *
         * @param {Object} options
         * @param {Object} options.style - mapbox style object
         * @param {Function} [options.sourceFilter] - sourceFilter is used to filter which source participate in pickFeature process.
         * @param {Number} [options.maximumLevel] - if cesium zoom level exceeds maximumLevel, layer will be invisible.
         * @param {Number} [options.minimumLevel] - if cesium zoom level belows minimumLevel, layer will be invisible.
         * @param {Number} [options.tileSize=512] - can be 256 or 512.
         * @param {Boolean} [options.hasAlphaChannel] -
         * @param {String} [options.credit] -
         *
         */
        constructor(options) {
          this.mapboxRenderer = new Mapbox.BasicRenderer({
            style: options.style,
          });
          this._originStyle = JSON.parse(JSON.stringify(options.style));
          this.ready = false;
          this.readyPromise = this.mapboxRenderer._style.loadedPromise.then(
            () => (this.ready = true)
          );
          this.tilingScheme = [4490, 4326].includes(options.wkid)
            ? new Cesium.GeographicTilingScheme()
            : new Cesium.WebMercatorTilingScheme();
          this.rectangle = this.tilingScheme.rectangle;
          this.tileSize =
            this.tileWidth =
            this.tileHeight =
              options.tileSize || 512;
          this.maximumLevel = options.maximumLevel || Number.MAX_SAFE_INTEGER;
          this.minimumLevel = options.minimumLevel || 0;
          this.tileDiscardPolicy = undefined;
          this.errorEvent = new Cesium.Event();
          this.credit = new Cesium.Credit(options.credit || "", false);
          this.proxy = new Cesium.DefaultProxy("");
          this.hasAlphaChannel =
            options.hasAlphaChannel !== undefined
              ? options.hasAlphaChannel
              : true;
          this.sourceFilter = options.sourceFilter;
        }

        getTileCredits(x, y, level) {
          return [];
        }

        createTile() {
          let canv = document.createElement("canvas");
          canv.width = this.tileSize;
          canv.height = this.tileSize;
          canv.style.imageRendering = "pixelated";
          canv.getContext("2d").globalCompositeOperation = "copy";
          return canv;
        }

        requestImage(x, y, zoom, releaseTile = true) {
          zoom += 1;
          if (zoom > this.maximumLevel || zoom < this.minimumLevel)
            return Promise.reject(undefined);

          this.mapboxRenderer.filterForZoom(zoom);
          const tilesSpec = [];
          this.mapboxRenderer.getVisibleSources().forEach((s) => {
            tilesSpec.push({
              source: s,
              z: zoom,
              x: x,
              y: y,
              left: 0,
              top: 0,
              size: this.tileSize,
            });
          });

          return new Promise((resolve, reject) => {
            let canv = this.createTile();

            const renderRef = this.mapboxRenderer.renderTiles(
              canv.getContext("2d"),
              {
                srcLeft: 0,
                srcTop: 0,
                width: this.tileSize,
                height: this.tileSize,
                destLeft: 0,
                destTop: 0,
              },
              tilesSpec,
              (err) => {
                if (!!err) {
                  switch (err) {
                    case "canceled":
                    case "fully-canceled":
                      reject(undefined);
                      break;
                    default:
                      reject(undefined);
                  }
                } else {
                  if (releaseTile) {
                    console.log(canv.toDataURL( 'image/png' ));
                    renderRef.consumer.ctx = undefined;
                    resolve(canv);
                    // releaseTile默认为true,对应Cesium请求图像的情形
                    this.mapboxRenderer.releaseRender(renderRef);
                  } else {
                    // releaseTile为false时在由pickFeature手动调用,在渲染完成之后在pickFeature里边手动释放tile
                    resolve(renderRef);
                  }
                }
              }
            );
          });
        }

        pickFeatures(x, y, zoom, longitude, latitude) {
          return this.requestImage(x, y, zoom, false).then((renderRef) => {
            let targetSources = this.mapboxRenderer.getVisibleSources();
            targetSources = this.sourceFilter
              ? this.sourceFilter(targetSources)
              : targetSources;

            const queryResult = [];

            longitude = Cesium.Math.toDegrees(longitude);
            latitude = Cesium.Math.toDegrees(latitude);

            targetSources.forEach((s) => {
              queryResult.push({
                data: this.mapboxRenderer.queryRenderedFeatures({
                  source: s,
                  renderedZoom: zoom + 1,
                  lng: longitude,
                  lat: latitude,
                  tileZ: zoom + 1,
                }),
              });
            });

            // release tile
            renderRef.consumer.ctx = undefined;
            this.mapboxRenderer.releaseRender(renderRef);
            return queryResult;
          });
        }

        destroy() {
          this.resetTile();
          this.mapboxRenderer._gl
            .getExtension("WEBGL_lose_context")
            .loseContext();
        }
        resetTile() {
          this.mapboxRenderer._cancelAllPendingRenders();
         
          Object.values(this.mapboxRenderer._style.sourceCaches).forEach(
            (cache) => {
              cache._tileCache.reset();
              cache._tileCache.remove();
            }
          );
        }

        setOnlyVisible(visible = true, layerId, field, fieldValue) {
          this.resetTile();
          const style = this.mapboxRenderer._style;
          const _order = style._order;
          style.setLayers(_order);
          _order.forEach(l=>style.setFilter(l));
          if (layerId || field) {
            this.mapboxRenderer._style.setLayers(
              visible ? [layerId] : _order.filter((l) => l !== layerId)
            );
            if(field){
            this.mapboxRenderer.setFilter(layerId,[visible?'==':'!=',field, fieldValue]);
            }
          }
        }
      }
      async function getGeosceneVecTile(arcVecUrl, isCache = false) {
        return Promise.all([
          fetch(arcVecUrl + "?f=json"),
          fetch(arcVecUrl + "/resources/styles"),
        ])
          .then(([r1, r2]) => Promise.all([r1.json(), r2.json()]))
          .then(([info, style]) => {
            const { wkid, latestWkid } = info.tileInfo.spatialReference;
            style.sprite = arcVecUrl + "/resources/sprites/sprite";
            style.glyphs =
              arcVecUrl + "/resources/fonts/{fontstack}/{range}.pbf";

            delete style.sources.esri.url;
            style.sources.esri.tiles = [`${arcVecUrl}/tile/{z}/{y}/{x}.pbf`];
            const provider = new MVTImageryProvider({
              style,
              wkid: wkid || latestWkid,
              isCache,
            });
            return provider.readyPromise.then(() => ({
              provider,
              layerIds: style.layers.map((l) => l.id),
            }));
          });
      }
    </script>
  </head>

  <body>
    <div id="cesiumContainer">loading...</div>
    <div
      id="cesiumToolBarDom"
      style="
        position: absolute;
        top: 10px;
        left: 10px;
        max-height: 200px;
        overflow-y: scroll;
        width: 30%;
        background: white;
        font-size: 12px;
      "
    ></div>
    <script>
      const arcgisVecUrl =  "https://basemaps.arcgis.com/arcgis/rest/services/World_Basemap_v2/VectorTileServer"
      window.onload = function () {
        // TODO: key code
        window.Mapbox = window["mapbox-gl"];
        window.cesiumContainer.innerText = "";
        Cesium.Ion.defaultServer = "";
        const cesiumViewer = new Cesium.Viewer("cesiumContainer", {
          infoBox: false,
          baseLayerPicker: false,
          terrainProvider: undefined,
          imageryProvider: new Cesium.GridImageryProvider(),
        });
        window.cesiumViewer = cesiumViewer;
        const RADIO_CLASS='RADIO_CLASS';
        getGeosceneVecTile(
         arcgisVecUrl
        ).then(({ provider, layerIds }) => {
          
          window.cesiumToolBarDom.innerHTML = "";
          let layer = cesiumViewer.imageryLayers.addImageryProvider(provider);
          const contents = layerIds
            .map(
              (i) =>
                `<div class="toolbar-item"><input class="${RADIO_CLASS}" type="radio" value="${i}">${i}</div>`
            )
            .join("");
          window.cesiumToolBarDom.innerHTML = `<div class="toolbar-item"><input type="radio" value="" class="${RADIO_CLASS}" checked >全部</div>${contents}`;
          window.cesiumToolBarDom.onclick = ({ target }) => {
        
            if (target?.nodeName === "INPUT" && target?.className ===RADIO_CLASS) {
              
            cesiumViewer.imageryLayers.remove(layer,true);
              Array.from(
                window.cesiumToolBarDom.getElementsByTagName("input")
              ).forEach((pt) => {
                pt.checked = pt === target ? true : false;
              });
              provider.setOnlyVisible(true, target.value);
              layer = cesiumViewer.imageryLayers.addImageryProvider(provider);
            }
          };
        });
      };
    </script>
  </body>
</html>

 

 

Tags (2)
1 Comment
wenzf
by

good!