Selecting multiple B3DM features

Hi all,

I'm trying to create a functionality in Cesium that allows people to select multiple B3DM features like buildings, etc, simply by click and dragging around an area.

2. A minimal code example. If you've found a bug, this helps us reproduce and repair it.

//rectangleSelector is a Cesium.Rectangle object
var n = rectangleSelector.north;
    var s = rectangleSelector.south;
    var e = rectangleSelector.east;
    var w = rectangleSelector.west;

    //longitude selector
    for (var i = Cesium.Cartesian3.fromDegrees(Math.min(e,w)); i < Cesium.Cartesian3.fromDegrees(Math.max(e,w));
      i += ( (Cesium.Cartesian3.fromDegrees(Math.max(e,w)) - Cesium.Cartesian3.fromDegrees(Math.min(e,w))) / 10)) {
      
      //latitude selector
      for (var j = Cesium.Cartesian3.fromDegrees(Math.min(n,s)); j < Cesium.Cartesian3.fromDegrees(Math.max(n,s));
        j += ( (Cesium.Cartesian3.fromDegrees(Math.max(n,s)) - Cesium.Cartesian3.fromDegrees(Math.min(n,s))) / 10)) {
        var pick = viewer.scene.pick(i, j);
        viewer.entities.add({
          position: startdraw,
          point : {
            pixelSize : 10,
            color : Cesium.Color.YELLOW
          }
        })
      }
    }

My goal is to visualize urban environments and apply certain data analysis algorithms to this to provide more semantic information to users. Right now, however, I am stuck on the visualization.

Running Cesium 1.37 on Google Chrome and Windows.

Thanks!!

Aditya Gune
Oregon State University

Just realized my question got pasted over by my code example. My code is attempting to take the area of the rectangle and perform a scene.pick on points throughout that rectangle. However, this does not actually register a feature picked as a Cesium3DTileFeature object. Does anyone know a better way to perform a click-drag selection of multiple features in a way that it will actually recognize that? Thanks!

Just to be sure, is the rectangle selection always going to be in terms of a longitude/latitude region, or does it need to be a rectangle drawn from the screen?

If the first case, the easiest way to do this is store longitude and latitude metadata in your b3dm tiles. Then you can traverse over the tileset and find which tiles are inside that rectangle, and then check individual features against the rectangle. I could write some quick example code if it’s helpful. Using scene.pick in this situation probably won’t work super well since you would need to convert the rectangle’s lat/long coordinates to window coordinates and then hope it actually picks all the features - it will likely miss a lot of them.

For the second case, doing multiple picks from a rectangle defined in window coordinates is probably the best way to go until we have a way of returning all the unique pick ids within a region. But like above this approach isn’t foolproof and it might miss some features.

Thanks for the reply Sean! It's a rectangle drawn on the screen by the user. The multiple pick approach is what I've been trying but like you said, I think it just doesn't hit some features.

Ah alright, unfortunately I don’t have a better solution then.

Sean, is there a way to access and search the feature table of a B3DM tile? I was wondering if that might allow for a more efficient way to search rather than doing pick calls on thousands of points. I've tried going through tile.content._features, but this seems to be endlessly recursive when I visualize it in Cesium.

Thanks,
Aditya

tile.content._features / content.getFeature is the right way to get data from the batch table. Is this still for selecting features?

If it helps, there was another thread with a slightly different case - to select all tiles within a range on the screen. Some of the code might be helpful to look at:

https://groups.google.com/forum/#!topic/cesium-dev/q9UYlnLhZzs

// Click to start selection, move mouse, then click again to end selection

var viewer = new Cesium.Viewer(‘cesiumContainer’);

var boxElement = document.getElementById(‘box’);

var scene = viewer.scene;

var camera = scene.camera;

var url = ‘…/…/…/Specs/Data/Cesium3DTiles/Tilesets/Tileset/’;

var tileset = scene.primitives.add(new Cesium.Cesium3DTileset({

url : url,

debugShowContentBoundingVolume : true

}));

tileset.readyPromise.then(function() {

var boundingSphere = tileset.boundingSphere;

viewer.camera.viewBoundingSphere(boundingSphere, new Cesium.HeadingPitchRange(0.0, -1.0, boundingSphere.radius));

viewer.camera.lookAtTransform(Cesium.Matrix4.IDENTITY);

});

var handler = new Cesium.ScreenSpaceEventHandler(viewer.canvas);

var start;

var end;

handler.setInputAction(function(movement) {

if (!Cesium.defined(start)) {

start = Cesium.Cartesian2.clone(movement.position, new Cesium.Cartesian2());

return;

}

if (Cesium.defined(start)) {

end = Cesium.Cartesian2.clone(movement.position, new Cesium.Cartesian2());

selectTiles(start, end);

start = undefined;

end = undefined;

boxElement.style.width = ‘0px’;

boxElement.style.height = ‘0px’;

}

}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

handler.setInputAction(function(movement) {

if (Cesium.defined(start)) {

var end = movement.endPosition;

var rectangle = getRectangle(start, end);

boxElement.style.left = rectangle.x + ‘px’;

boxElement.style.top = rectangle.y + ‘px’;

boxElement.style.width = rectangle.width + ‘px’;

boxElement.style.height = rectangle.height + ‘px’;

}

}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

function getRectangle(start, end) {

var xMin = Math.min(start.x, end.x);

var xMax = Math.max(start.x, end.x);

var yMin = Math.min(start.y, end.y);

var yMax = Math.max(start.y, end.y);

var width = xMax - xMin;

var height = yMax - yMin;

return new Cesium.BoundingRectangle(xMin, yMin, width, height);

}

function getCenter(rectangle) {

return new Cesium.Cartesian2(rectangle.x + rectangle.width / 2.0, rectangle.y + rectangle.height / 2.0);

}

function selectTiles(start, end) {

start = Cesium.SceneTransforms.transformWindowToDrawingBuffer(scene, start, start);

end = Cesium.SceneTransforms.transformWindowToDrawingBuffer(scene, end, end);

var rectangle = getRectangle(start, end);

var center = getCenter(rectangle);

var cullingVolume = getCullingVolume(center, rectangle.width, rectangle.height);

var selected = ;

traverseTileset(tileset._root, cullingVolume, selected);

var color = Cesium.Color.fromRandom({alpha:1.0});

var length = selected.length;

for (var i = 0; i < length; ++i) {

selected[i].color = color;

}

}

function traverseTileset(tile, cullingVolume, selected) {

var boundingVolume = tile._boundingVolume;

var contentBoundingVolume = tile._contentBoundingVolume;

if (!intersectsBoundingVolume(cullingVolume, boundingVolume)) {

// Stop traversal early since children won’t be visible either

return;

}

if (!Cesium.defined(contentBoundingVolume) || intersectsBoundingVolume(cullingVolume, contentBoundingVolume)) {

// Test intersection with the content bounding volume

selected.push(tile);

}

var children = tile.children;

var length = children.length;

for (var i = 0; i < length; ++i) {

traverseTileset(children[i], cullingVolume, selected);

}

}

function intersectsBoundingVolume(cullingVolume, boundingVolume) {

return cullingVolume.computeVisibility(boundingVolume) !== Cesium.Intersect.OUTSIDE;

}

function getCullingVolume(center, width, height) {

// Adapted from getPickPerspectiveCullingVolume in Scene.js

var screenWidth = scene.drawingBufferWidth;

var screenHeight = scene.drawingBufferHeight;

var frustum = camera.frustum;

var near = frustum.near;

var far = frustum.far;

var tanPhi = Math.tan(frustum.fovy * 0.5);

var tanTheta = frustum.aspectRatio * tanPhi;

var x = 2.0 * center.x / screenWidth - 1.0;

var y = 2.0 * (screenHeight - center.y) / screenHeight - 1.0;

var xDir = x * near * tanTheta;

var yDir = y * near * tanPhi;

var pixelSize = frustum.getPixelDimensions(screenWidth, screenHeight, near, new Cesium.Cartesian2());

var pickWidth = pixelSize.x * width * 0.5;

var pickHeight = pixelSize.y * height * 0.5;

var offCenter = new Cesium.PerspectiveOffCenterFrustum();

offCenter.top = yDir + pickHeight;

offCenter.bottom = yDir - pickHeight;

offCenter.right = xDir + pickWidth;

offCenter.left = xDir - pickWidth;

offCenter.near = near;

offCenter.far = far;

return offCenter.computeCullingVolume(camera.positionWC, camera.directionWC, camera.upWC);

}

``