Switching View 2D to 3D with Feature Layer persists graphics

938
2
Jump to solution
09-17-2018 10:16 PM
JordanKing3
New Contributor III

Hey Guys,

I'm wondering if the following is a bug?

We have an app in which we have a single Map object, which is used to drive two views, one 2D and one 3D. We followed the sample on switching between 2D and 3D (Switch view from 2D to 3D | ArcGIS API for JavaScript 4.8 ), as our app only ever displays one view or the other.

The situation I have come across is this; if I add an in-memory Feature Layer, with a custom graphic source, to my Map in the 2D view. Then I switch to the 3D view, but before I do so, I remove that layer from the map before the 3D view becomes the active view. The graphics from the Feature Layer still persist in the map within the 3D view, even though the map.layers.items is zero and there are no view graphics etc. It actually happens if we add the Feature Layer whilst the 3D view is active too, then switch to the 2D view and then back to the 3D view and then remove that layer.

I've taken the 2D/3D switch sample and changed it to demonstrate the issue: Switch view from 2D to 3D - 4.8 - Feature Layer Issue? 

Is there something else we should be doing? or is this likely a bug?

We are experiencing it at 4.6, however, the sample above is at 4.8.

Cheers,

Jordan

0 Kudos
1 Solution

Accepted Solutions
RobertScheitlin__GISP
MVP Esteemed Contributor

Jordan,

  The issue is your on event is getting added again and again to your app instead of just once.

Here is what I have working.

<html>
<head>

  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <!--
  ArcGIS API for JavaScript, https://js.arcgis.com
  For more information about the views-switch-2d-3d sample, read the original sample description at developers.arcgis.com.
  https://developers.arcgis.com/javascript/latest/views-switch-2d-3d/index.html
  -->
<title>Switch view from 2D to 3D - 4.8</title>

  <style>
    html,
  body,
  #viewDiv {
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
  }

  #infoDiv{
    position: absolute;
    top: 15px;
    left: 60px;
  }

  #infoDiv input {
    border: none;
    box-shadow:  rgba(0, 0, 0, 0.3) 0px 1px 2px;
  }
</style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.8/esri/css/main.css">
  <script src="https://js.arcgis.com/4.8/"></script>

  <script>
    require([
      "esri/views/MapView",
      "esri/views/SceneView",
      "esri/WebMap",
      "esri/WebScene",
      "esri/Map",
      "esri/layers/FeatureLayer",
      "esri/core/Collection",
      "esri/Graphic",
      "dojo/on",
      "dojo/domReady!"
    ], function (MapView, SceneView, WebMap, WebScene, Map, FeatureLayer, Collection, Graphic, on) {
      var switchButton = document.getElementById("switch-btn");

      var appConfig = {
        mapView: null,
        sceneView: null,
        activeView: null,
        container: "viewDiv"  // use same container for views
      };

      var map = new Map({
        basemap: 'streets',
        ground: 'world-elevation'
      });

      var initialViewParams = {
        zoom: 4,
        center: [-100.43759993450347, 40.772798684981126],
        container: appConfig.container
      };
      var webmap = new WebMap({
        map: map
      });

      var scene = new WebScene({
        map: map
      });

      var graphics = new Collection();

      var polyline = {
        type: "polyline",  // autocasts as new Polyline()
          paths: [
            [-111.30, 52.68],
            [-98, 49.5],
            [-93.94, 29.89]
          ]
      };

      var polylineSymbol = {
        type: "simple-line",  // autocasts as SimpleLineSymbol()
        color: [226, 119, 40],
        width: 4
      };

      var polylineAtt = {
        Name: "Keystone Pipeline",
        Owner: "TransCanada"
      };

      var polylineGraphic = new Graphic({
        geometry: polyline,
        symbol: polylineSymbol,
        attributes: polylineAtt
      });

      graphics.add(polylineGraphic);

      // Create an in-memory Feature Layer
      var fl = new FeatureLayer({
        id: 'test',
        source: graphics,
        fields: [
          {
            name: "ObjectID",
            alias: "ObjectID",
            type: "oid"
          },
          {
            name: "Owner",
            alias: "Owner",
            type: "string"
          },
          {
            name: "Name",
            alias: "Name",
            type: "string"
          }
        ],
        objectIdField: 'ObjectID',
        geometryType: 'polyline',
        spatialReference: { wkid: 4326 }
      });

      // Add the Feature Layer to the map
      map.add(fl);

      // create 2D view and and set active
      appConfig.mapView = createView(initialViewParams, "2d");
      appConfig.mapView.map = map;
      appConfig.activeView = appConfig.mapView;

      // create 3D view, won't initialize until container is set
      initialViewParams.container = null;
      initialViewParams.map = map;
      appConfig.sceneView = createView(initialViewParams, "3d");

      // switch the view between 2D and 3D each time the button is clicked
      switchButton.addEventListener("click", function(){
        switchView();
      });

      // Switches the view from 2D to 3D and vice versa
      function switchView(){
        var is3D = appConfig.activeView.type === "3d";

        // remove the reference to the container for the previous view
        appConfig.activeView.container = null;

        if (is3D){
          // if the input view is a SceneView, set the viewpoint on the
          // mapView instance. Set the container on the mapView and flag
          // it as the active view
          appConfig.mapView.viewpoint = appConfig.activeView.viewpoint.clone();
          appConfig.mapView.container = appConfig.container;
          appConfig.activeView = appConfig.mapView;
          switchButton.value = "3D";
          map.add(fl);
        } else {
          appConfig.sceneView.viewpoint = appConfig.activeView.viewpoint.clone();
          appConfig.sceneView.container = appConfig.container;
          appConfig.activeView = appConfig.sceneView;
          switchButton.value = "2D";
          on.once(fl, 'layerview-create', function() {
            map.remove(fl);
          });
        }
      }

      // convenience function for creating a 2D or 3D view
      function createView(params, type){
        var view;
        var is2D = type === "2d";
        if(is2D){
          view = new MapView(params);
          return view;
        } else {
          view = new SceneView(params);
        }
        return view;
      }

    });
  </script>

</head>

<body>
  <div id="viewDiv"></div>
  <div id="infoDiv">
    <input class="esri-component esri-widget--button esri-widget esri-interactive"
      type="button" id="switch-btn" value="3D">
  </div>
</body>
</html>

View solution in original post

0 Kudos
2 Replies
RobertScheitlin__GISP
MVP Esteemed Contributor

Jordan,

  The issue is your on event is getting added again and again to your app instead of just once.

Here is what I have working.

<html>
<head>

  <meta charset="utf-8">
  <meta name="viewport" content="initial-scale=1,maximum-scale=1,user-scalable=no">
  <!--
  ArcGIS API for JavaScript, https://js.arcgis.com
  For more information about the views-switch-2d-3d sample, read the original sample description at developers.arcgis.com.
  https://developers.arcgis.com/javascript/latest/views-switch-2d-3d/index.html
  -->
<title>Switch view from 2D to 3D - 4.8</title>

  <style>
    html,
  body,
  #viewDiv {
    padding: 0;
    margin: 0;
    height: 100%;
    width: 100%;
  }

  #infoDiv{
    position: absolute;
    top: 15px;
    left: 60px;
  }

  #infoDiv input {
    border: none;
    box-shadow:  rgba(0, 0, 0, 0.3) 0px 1px 2px;
  }
</style>

  <link rel="stylesheet" href="https://js.arcgis.com/4.8/esri/css/main.css">
  <script src="https://js.arcgis.com/4.8/"></script>

  <script>
    require([
      "esri/views/MapView",
      "esri/views/SceneView",
      "esri/WebMap",
      "esri/WebScene",
      "esri/Map",
      "esri/layers/FeatureLayer",
      "esri/core/Collection",
      "esri/Graphic",
      "dojo/on",
      "dojo/domReady!"
    ], function (MapView, SceneView, WebMap, WebScene, Map, FeatureLayer, Collection, Graphic, on) {
      var switchButton = document.getElementById("switch-btn");

      var appConfig = {
        mapView: null,
        sceneView: null,
        activeView: null,
        container: "viewDiv"  // use same container for views
      };

      var map = new Map({
        basemap: 'streets',
        ground: 'world-elevation'
      });

      var initialViewParams = {
        zoom: 4,
        center: [-100.43759993450347, 40.772798684981126],
        container: appConfig.container
      };
      var webmap = new WebMap({
        map: map
      });

      var scene = new WebScene({
        map: map
      });

      var graphics = new Collection();

      var polyline = {
        type: "polyline",  // autocasts as new Polyline()
          paths: [
            [-111.30, 52.68],
            [-98, 49.5],
            [-93.94, 29.89]
          ]
      };

      var polylineSymbol = {
        type: "simple-line",  // autocasts as SimpleLineSymbol()
        color: [226, 119, 40],
        width: 4
      };

      var polylineAtt = {
        Name: "Keystone Pipeline",
        Owner: "TransCanada"
      };

      var polylineGraphic = new Graphic({
        geometry: polyline,
        symbol: polylineSymbol,
        attributes: polylineAtt
      });

      graphics.add(polylineGraphic);

      // Create an in-memory Feature Layer
      var fl = new FeatureLayer({
        id: 'test',
        source: graphics,
        fields: [
          {
            name: "ObjectID",
            alias: "ObjectID",
            type: "oid"
          },
          {
            name: "Owner",
            alias: "Owner",
            type: "string"
          },
          {
            name: "Name",
            alias: "Name",
            type: "string"
          }
        ],
        objectIdField: 'ObjectID',
        geometryType: 'polyline',
        spatialReference: { wkid: 4326 }
      });

      // Add the Feature Layer to the map
      map.add(fl);

      // create 2D view and and set active
      appConfig.mapView = createView(initialViewParams, "2d");
      appConfig.mapView.map = map;
      appConfig.activeView = appConfig.mapView;

      // create 3D view, won't initialize until container is set
      initialViewParams.container = null;
      initialViewParams.map = map;
      appConfig.sceneView = createView(initialViewParams, "3d");

      // switch the view between 2D and 3D each time the button is clicked
      switchButton.addEventListener("click", function(){
        switchView();
      });

      // Switches the view from 2D to 3D and vice versa
      function switchView(){
        var is3D = appConfig.activeView.type === "3d";

        // remove the reference to the container for the previous view
        appConfig.activeView.container = null;

        if (is3D){
          // if the input view is a SceneView, set the viewpoint on the
          // mapView instance. Set the container on the mapView and flag
          // it as the active view
          appConfig.mapView.viewpoint = appConfig.activeView.viewpoint.clone();
          appConfig.mapView.container = appConfig.container;
          appConfig.activeView = appConfig.mapView;
          switchButton.value = "3D";
          map.add(fl);
        } else {
          appConfig.sceneView.viewpoint = appConfig.activeView.viewpoint.clone();
          appConfig.sceneView.container = appConfig.container;
          appConfig.activeView = appConfig.sceneView;
          switchButton.value = "2D";
          on.once(fl, 'layerview-create', function() {
            map.remove(fl);
          });
        }
      }

      // convenience function for creating a 2D or 3D view
      function createView(params, type){
        var view;
        var is2D = type === "2d";
        if(is2D){
          view = new MapView(params);
          return view;
        } else {
          view = new SceneView(params);
        }
        return view;
      }

    });
  </script>

</head>

<body>
  <div id="viewDiv"></div>
  <div id="infoDiv">
    <input class="esri-component esri-widget--button esri-widget esri-interactive"
      type="button" id="switch-btn" value="3D">
  </div>
</body>
</html>
0 Kudos
JordanKing3
New Contributor III

Hey Robert,

Thanks for taking the time to look at this.

In the end, I managed to find my issue, which was actually a issue within my application. The app we're building had a few more intricacies to it, which made the switch between 2D/3D a bit more complicated. Specifically, for some of our data, we use a different renderer in 2D and 3D (or Feature Layer in 3D and Map Image Layer in 2D), so we can support 3D symbols in 3D. This meant that we needed to go through and remove certain layers from the map, before we did anything with the views. 

What I had done wrong was set the view.container = null, before doing any of the map layer removes. Now, I have it all chained so that the layer removing is completed before I do anything with the view.

I'll mark your answer as correct, as the help you provided was good advice.

Cheers,

Jordan

0 Kudos