Determine which billboards are currently viewable

What is the best way to determine which billboards are visible in the current camera view?

I am working on a project where I have very large data set that I am required to draw as points initially (I'm actually using billboards to draw the points so I can change their scale by distance). These points are not static, and their positions will update based on data coming in from the server. I have been asked to change the point graphics to more complex mil symbol graphics when there are fewer than a certain number of points on the screen. So for example, when there are less than 100 points viewable, I will turn them into larger graphics. This is not based on zoom level, it's only based on the number of points in the current view.

I was thinking of doing something like this:

1) At every clock tick, get the current bounds of the viewable area using the technique posted here (https://groups.google.com/forum/#!topic/cesium-dev/VL_KVvLrELM)

2) Cycling through my billboard collections to compare the lat/lon values to the bounds obtained in step 1

3) If there are less than my threshold, cycle through the viewable billboards obtained in step 2 and change their images to be the proper mil symbol.

4) Revert any billboards not in the viewable area back to their point graphic

This feels inefficient, doesn't work when the user is zoomed out enough so that sky is in the view, and I know there are internal calculations going on that are much better than anything I will come up with that already determine which objects to display for the current camera view, so I'm hoping there's a way to get to that data.

I'm bumping this one in the hopes that there might be a solution :slight_smile: I've tried a few things to try and hack this (as mentioned in my original post), but none of them work acceptably, and I really need this capability. Is there any way to get the list of billboards that are currently in view? Even if I need to tweak some of the actual API code to expose something, I'm willing to try that.

Hi, I just posted a message saying that I am in need of this. However, I came up with an idea right after I posted it.

Here is how I would do it. The following example is from my previous post at “Getting the current position of billboard”. This will get you the coordinates based on your screen (canvas) with x=0 y=0 being top left of screen.

var myPosition = billboard.computeScreenSpacePosition(scene);
// Returns X and Y of canvas… top-left screen being (0,0)
var cartesian = scene.camera.pickEllipsoid(myPosition, ellipsoid);
// Takes the X and Y based on canvas and converts it to x,y,z of the map
var cartographic = ellipsoid.cartesianToCartographic(cartesian);
// Takes x,y,z of map and converts to long,lat,height

By using the following 3 step conversion from Canvas Coordinates to actual coordinates, I’m sure you can check which billboards coordinates are within that rectangle. This is a good try, right? Hey, if you do get it, please post the answer. I am in need of this myself but it’s second on my agenda.

-Katone

Hi Katone - just FYI, I did try that, but the problem I ran into was the fact that billboards that are behind the globe (in 3D mode) still list their x,y coordinate as exactly where they would be if they were visible. So for example, if I'm looking at the United States, and there are billboards on the opposite side of the world, the x,y screen coordinates of those are the same as the ones in the United States, even though they're not actually visible because they're on the other side of the world.

That’s strange. It doesn’t seem to make sense to me since the coordinates of one side of the world shouldn’t be identical to the other. Other than that, I really don’t have any other thoughts of how to handle it. =( I just started this a week or two ago. If you ever do get the answer, feel free to share for I need it to. Once I’m done with my current task, I shall look into this with you =) I’ll try my method first to understand why that strange event is occurring. My only guess of to why this doens’t work well is because your camera is inbetween the +170 degrees to -170 degrees. I’m on the picking example, and at coordinates 178 lon 33 lat, i can just move my mouse to the right just a little bit and i’m on -178 lon 33 lat. The if and then statement must be different here. But as I have said, I don’t know this too well.

Katone

Oh sorry, I thought you meant the x,y screen pixel coordinates. You're correct, the world coordinates would be different, but determining the bounding sphere of the globe, and then dealing with all the weird cases (poles, lat/lon sign switches, etc), turned out to be somewhat computationally expensive when I tried it, and it still wasn't perfectly accurate. I figured since under the hood Cesium has already done the fancy math for the culling, it would be a lot faster for me to tap into that.

In the meantime, I'm going to try the idea posted in this discussion and I'll let you know how it goes.

https://groups.google.com/forum/#!msg/cesium-dev/z8HSjI2YGww/4odgox5YlcsJ

I've got some code working for this now - I will post it later tonight.

Share the answer? =) Unless you don’t want to, then it’s fine. But I would really appreciate it…

I'm always willing to share! :slight_smile: Sorry I've taken so long to post the code I have, the problem is that I can't get it to work in 2D mode, but it does work in 3D mode. (I posted my 2D issue here: https://groups.google.com/forum/#!searchin/cesium-dev/CullingVolume.ComputerVisibility/cesium-dev/GpR2A-lDonk/Qc_Xk3cwod4J but I don't have any responses for it yet).

I will post it anyway for you (it's currently on my computer at work so I'll get it tomorrow). Let me know if you have any ideas about the 2D mode, I may have to just try using the X,Y screen coordinates to get it working in 2D, not sure if that will work or not, but I'll try it tomorrow.

The method that I mentioned earlier seems to be working well for all 3 modes for me. The only requirement is that the GLOBE must cover the entire screen-canvas. Else, it would go undefine.

Hey Katone, can you post what check you performed in your method? I am trying to do what you're doing for 2D mode, but it doesn't seem to be working perfectly. Here is my code that works for 3D mode (and it doesn't matter if the entire globe is in view or not, which is a requirement for me):

var frustum = camera.frustum;
var cameraPosition = camera.position;
var cullingVolume = frustum.computeCullingVolume(cameraPosition, camera.direction, camera.up);
var occluderEllipsoid = Cesium.Ellipsoid.WGS84;
var occluder = new Cesium.EllipsoidalOccluder(occluderEllipsoid, cameraPosition);

var isPointVisible = occluder.isPointVisible(billboard.position);
var isInView = cullingVolume.computeVisibility(new Cesium.BoundingSphere(billboard.position, 0.0)) === Cesium.Intersect.INSIDE;
          
if( (billboard.show == true) && isPointVisible && isInView)
... This billboard is in view, do whatever logic here

Oops nevermind my question, I figured it out. In 2D, I can just computer the screenSpaceCoordinates for each billboard and compare to canvas.width and canvas.height.

Also just wanted to note that none of these solutions that require cycling through every billboard are very efficient (especially since the whole reason I'm doing this is because I may have tons of billboards, in the 10's of thousands up to 100,000 and I'm trying to display basic circles if there are too many on the screen, but then switch to real images when there are fewer than a certain amount in view, to help with performance). It would be totally awesome if there was just a BillboardCollection.getVisibleBillboards() function, since Cesium is already doing the work first :slight_smile:

Cesium does not actually know what billboards are visible. It only knows if any single billboard in a collection is visible and then everything else is done on the GPU and none of that information is retrievable.

Replacing billboards images with circle images will not help performance in any way.

Attempting to detect what visualization is on the screen after the fact is almost always the wrong way to go about whatever problem you are encountering. (View based visualization where you retrieve data or only create data for the visible view is a different story, i.e. streaming terrain/imagery/large vector data sets).

If you can explain your exact use case, I’d be happy to suggest some possible solutions; what feature/behavior are you trying to implement?

Hello there, buddy.

After getting started on this, i found out that there is no need for any checks because they all fall into places perfectly. I only need that simple if startCoord < currentPosition && endCoord > currentPosition. The rotation all fall into places on its own.

PS! I can literally run 350 000 billboards without issues. Cesium is capable of holding that much alone (if not more). When I add more, however, the globe seems to black out. The performance is still good. as well. However... czmlDataSources (10,000) gets laggy enough. This is when I actually need this =)

I should add that the method that I suggested work on all 3 modes. However, the globe MUST cover the entire canvas screen of Cesium in order for it to work... else it goes "undefined"

Hello Matthew, thank you for your response. I definitely appreciate any suggestions you may have. I am working on a project where I am plotting lots of items on the map (up to 100,000), and each one requires a symbol drawn dynamically based on various attributes. I am using a 3rd party library to generate the proper symbol for each item, and while it's pretty fast, it definitely can't handle the number of items I need to generate at once (while many are duplicates, there can still be thousands of unique images).

Therefore, for the initial load, I am just drawing circles (a 2D canvas with a circle drawn on it, loaded into billboards) for performance reasons. The requirement I have is to then convert those circles to their actual images when there are fewer than a certain (user defined) amount of items in view. Once there are too many of the real images on the screen, it becomes too much for the user to visually process anyway, so even if I could render all 100,000 images at once with the 3rd party library, they wouldn't want me to.

Note that my billboards are also moving in near-real time.

On top of this, they will eventually want decluttering, so I've been tracking the Cesium declutter roadmap with interest.

I'd love to find a better way to accomplish this since what I'm doing right now is very inefficient. I've done some things to mitigate the amount of times it has to do this (such as only checking the number of items when the map has been still for a couple of seconds and has moved significantly since the last time it was checked, etc), but I'd much prefer to have a more efficient solution in the first place.

Thank you!

Hello Katone, I also had issues with czmlDataSource performance in the beginning of my current project, which is why I switched to managing the billboards manually. I used czmlDataSource in a previous project though where there were fewer items on the screen, updating in near-real time, and it worked well for that.

That's awesome that you can draw 350,000 billboards! What kind of image are you using to draw so many, and how are you loading them initially? I'm not sure I understand your startCoord < currentPosition algorithm, are you willing to post more of the algorithm? I need to be able to perform my operation even if the corners are undefined (which caused issues for some of my previous attempts), but the code I posted up there will work even in that situation if you ever need it.

That algorithm is just exactly what i posted.

if (startCoord < billboardsCoord && endCoord > billboardsCoord){
// Perform billboards that appears here, whether it’s iterating through it or having a server to filter it real-time (like you mentioned)
}

Sadly, the method doesn’t work if the corners are undefined (The method that i suggested). And regarding the 350 000 billboards, you can check that out at Sandcastle, under Billboards -> Add point billboard. Run a forloop on it, set it to 350 000 with random coordinates and watch it populate. I also used actual icons i downloaded from online. Still smooth.