Placing buildings on the terrain

Just to coalesce a couple of threads
https://groups.google.com/forum/#!topic/cesium-dev/4rLKtasnubY

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

The reference ellipsoid doesn’t clip building basements at many camera tilt ranges, so it’s best to use terrain that does clip. To manually toggle terrain first press the imagery button on the top right, then scroll down to the terrain section (perhaps terrain should have it’s own button?) I’ve created a SandCastle app that allows one to move a building up and down(do full-screen to access all buttons.)

-Zoom out and try it with terrain on and with terrain off. With terrain off set ellipsoid+30 then move down, you’ll notice that there’s no clipping with the reference ellipsoid (well depending on the camera tilt angle.)

-With terrain on tHeight + 30 places the bottom on the terrain and the center is 30 meters above the bottom.

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

var CC3 = Cesium.Cartesian3;

var tLon = -90.1922703175/180*Math.PI;

var tLat = 38.6286636758/180*Math.PI;

var mycarto = new Cesium.Cartographic();

mycarto.longitude=tLon;mycarto.latitude=tLat;mycarto.height=0;

console.log(mycarto); //defined

var tHeight=viewer.scene.globe.getHeight(mycarto);

console.log(tHeight); //undefined, but why?

tHeight=110.139218977672; //just manually set for now

var thebox = viewer.entities.add({

name : ‘building’,

position: Cesium.Cartesian3.fromDegrees(-90.1922703175, 38.6286636758, 30),

box : {

dimensions : new Cesium.Cartesian3(40, 30, 60),

material : Cesium.Color.RED,

outline : true,

outlineColor : Cesium.Color.BLACK

}

});

viewer.zoomTo(viewer.entities);

function gohere(height)

{

var test = new Cesium.Cartographic(tLon,tLat,height);

var mypos = Cesium.Ellipsoid.WGS84.cartographicToCartesian(test);

thebox.position = mypos;

}

function upAndDown(scalar)

{

var mycarte = thebox._position._value;

var mynormal = Cesium.Ellipsoid.WGS84.geodeticSurfaceNormal(mycarte);

var relMove = CC3.multiplyByScalar(mynormal,scalar,new CC3());

CC3.add(mycarte,relMove,mycarte);

thebox.position = mycarte;

}

Sandcastle.addToolbarButton(‘up 5 meters’, function()

{upAndDown(5);});

Sandcastle.addToolbarButton(‘down 5 meters’, function()

{upAndDown(-5);});

//note that this is center of the building, not the bottom

Sandcastle.addToolbarButton(‘ellipsoid+30’, function()

{gohere(0+30);});

Sandcastle.addToolbarButton(‘tHeight+10’, function()

{gohere(tHeight+10);});

Sandcastle.addToolbarButton(‘tHeight+20’, function()

{gohere(tHeight+20);});

Sandcastle.addToolbarButton(‘tHeight+30’, function()

{gohere(tHeight+30);});

Sandcastle.addToolbarButton(‘tHeight+40’, function()

{gohere(tHeight+40);});

Sandcastle.addToolbarButton(‘tHeight+50’, function()

{gohere(tHeight+50);});

``

If anyone knows why var tHeight=viewer.scene.globe.getHeight(mycarto); is failing please let me know.

Thank you very much for the great example!

I can’t test it on my own project right now, but as far as I understand the code it should be possible to position the model exactly on the ground (terrain) as soon as the getHeight() function works (get the height at the lat/lon and use it for the positioning (height) of the model).

Thanks, this really looks useful, I am going to try to make it work in our setting.

I’m glad you found it useful! I still don’t understand why viewer.scene.globe.getHeight isn’t working, it works just fine in my plugin, and I used it in SandCastle earlier to obtain the terrain height.

I just have to say thanks again, It looks like you pointed me at a solution to our biggest problem.

The URL is http://vcities.ite-stl.org/Cesium/Apps/Sandcastle/gallery/Cesiumtest.php and when you switch to the STK terrain the dreaded basements go away.

Sorry,

http://vcities.ite-stl.org/Cesium/Apps/Sandcastle/gallery/Cesiumtest1.php

is a better link. Go to 1900.

That’s pretty neat, scrolling the years from 1865 to 2030 you can see buildings pop in and out of existence. I’m glad it’s working well now. If one wanted to see the basements they could always turn off the terrain, except for places like Death Valley and the Dead Sea where it’s reversed (terrain is under the ellipsoid.) Actually places like Death Valley still pose a problem, the tile under you disappears when you’re 90 meters or less above the terrain since that’s when the camera goes under the ellipsoid.

Related to the getHeigh() function:

According to this entry it should work with sampleTerrain(); so I tried an adapted version of this code (see below for sandcastle) and it returns me perfectly the height of a given position.

The problem is now that there is a small “lag” until the height is available in the variable and can be used for the model placing. So I was searching for a way to “stop” the code until the height is available in the variable. I was playing around with while (result: browser crash) and setTimeout() (does not stop the code, so the rest continues) as well as setInterval() (gets an error with if/else -> resets the changed variable.

var terrainProvider = new Cesium.CesiumTerrainProvider({

url : ‘//assets.agi.com/stk-terrain/world’,

requestVertexNormals : true

});

var test = 1500; //the altitude variable that should be changed to the value at pos lat/long

var viewer = new Cesium.Viewer(‘cesiumContainer’,

{terrainProvider: terrainProvider, baseLayerPicker: false});

var ellipsoid = viewer.scene.globe.ellipsoid; //just necessary to fly there.

var POI= Cesium.Cartographic.fromDegrees(46.5,9.5,0, new Cesium.Cartographic()); //the position in lat/long as well as height 0.

viewer.camera.flyTo({destination: ellipsoid.cartographicToCartesian(POI, new Cesium.Cartesian3())});

var promise = Cesium.sampleTerrain(terrainProvider, 11, [POI]); //takes the terrain, a precision value and the point of interest (lat/long; returns a promise.

Cesium.when(promise, function(POIx) { //updates the test variable with the “real” variable (i.e. the altitude at pos. lat/long.

test = (POIx[0].height);

});

setTimeout(function(){console.log(test)},0); //returns immediately the value --> returns 1500; the defined variable

setTimeout(function(){console.log(test)},1000); //returns the value after 1 second --> returns 747; the “real” value at that position.

``

Since I’m drawing all my models in a loop I need a way to “halt” the function after I update the altitude (since that needs some time) before I can continue with drawing the model with the now real altitude. Right now I get the value, but always too late -> the models are already drawn at the defined height 1500.

If anybody knows a way to give the code some time, thanks!

Regards

Martin

I had the same problem and I have been using a version of the code below.

function loadPause(){
if (pausetime>0) {
pausetime–;
somedots=somedots+" .";
setdesc(“Loading Buildings Please Wait\n (loading time approximate)\n\n”+somedots);
self.setTimeout(“loadPause()”, 1000);
} else {


}
}

The right way to use sampleTerrain is to use the promise returned from calling it. Network requests are asynchronous by nature and any attempt to try and make the synchronous is flawed. setTimeout will create race conditions and and are also inefficient. There’s an example in the documentation: http://cesiumjs.org/Cesium/Build/Documentation/sampleTerrain.html

Just an FYI that clamping things to the ground is on the near term roadmap for Cesium (I was actually hoping we’d have it by now but there’s been some hurdles). Putting a model or anything else on the ground or relative to the ground will be trivial in the next few months.

Will Cesium clamp to ground automatically adapt to tiles changing LOD levels by chance? Maybe when a tile changes LOD, it tags all entities on it to have their heights re-adjusted.

Yes.

@Matthew: Thanks for the clarification.

I tried it now in this form (sampleTerrain() … .then(function(){here the whole drawing of the model}). This gives me the model at the right position and altitude. But: a) I still have quite a big parallax-effect, i.e. the model is “moving” around and not clamped to the ground; and b) only the last model from the array is drawn, i.e. the loop takes first all variables from the array (and goes through all entry in the array) and then the sampleTerrain works only for the last entry in the array. So as far as I understand there is still a problem with this “lag” during the loop, even if I take the result from the sampleTerrain not “outside” of it as a variable but within the .then-part.

You can send many points in just one call to sampleTerrain
http://cesiumjs.org/Cesium/Build/Documentation/sampleTerrain.html

So call sampleTerrain just one time, then while waiting for the height data place the buildings at the proper lon/lat and whatever height. Then once you get the height data adjust the heights of each of the buildings to the proper value.

One problem with sampleTerrain is that it returns the height at a certain tile LOD, but the tile LOD varies depending on factors such as camera distance.

The parallax effect should only occur if you have terrain off and the building is either buried or floating over the reference ellipsoid. When Cesium has clampToGround I’d assume you’d be able to define what is considered the ‘terrain bottom’ of the model, which isn’t always the always the bottom of the model as some buildings have basements.

Ciao

Puh, now it is definitely too complicated for me.

How can I move the model after I get back the heigh data?

Hm, terrain is activated and I still get this effect, since the area there is quite flat I do not think it should have anything to do with a wrong position (but maybe with a wrong z-LOD of course, but I though I can omit this with a high precision during the sampling.).

You can do
viewer.scene.globe.ellipsoid.cartesianToCartographic
change the height then
viewer.scene.globe.ellipsoid.cartographicToCartesian
and set the model to the result.

Using the STK terrain? If the terrain is fairly flat then there probably isn’t much difference between the various LOD tile heights.

Yes, the STK terrain server.

Hm, but I don’t get how I can first load the height for all my positions and then draw the models on those position (+height). I tried to push the result from the sampleTerrain to my array to retrieve it later, but this does not work (not stored at all). I just do not understand how I can retrieve the result later if it is not explicit stored after the sampleTerrain for all positions is done (for only one position and one model it is straightforward).

The parallax effect is way stronger than when I correct it a bit manually; and it is definitely not drawn on the ellipsoid since the altitude at this position is somewhere around 500 meters (when it is drawn on the ellipsoid it would be way below the surface/terrain).

Maybe you can pre-process the relative to ellipsoid heights of all your models using sampleTerrain beforehand instead.

A good way to determine what tile LOD you should shoot is by checking out this URL http://www.agi.com/products/stk/terrain-server/

Check mark show tile coordinates, zoom in, and the tile level is listed after the L.

Using sampleTerrain is pretty easy, I just copied this from the reference docs and put it into a SandCastle app. 2 samples with one query.

var viewer = new Cesium.Viewer(‘cesiumContainer’);
var terrainProvider = new Cesium.CesiumTerrainProvider({
url : ‘//assets.agi.com/stk-terrain/world
});
var positions = [
Cesium.Cartographic.fromDegrees(86.925145, 27.988257),
Cesium.Cartographic.fromDegrees(87.0, 28.0)
];
var promise = Cesium.sampleTerrain(terrainProvider, 11, positions);
Cesium.when(promise, function(updatedPositions) {
console.log("pos0 "+positions[0].height);
console.log("pos1 "+positions[1].height);
});

``

You can .clone() positions array to your own array.

Actually use .slice to duplicate an array, not .clone, sorry.

Hello

Thank you for the great inputs!

Now it works, not exactly as the example here suggests but similar:

  • one array with all my models as objects with the coordinates stored
  • loop through this array and push the (transformed) coordinates to another array
  • sampleTerrain on the second array to get the altitude
  • wait for 10 seconds, then loop through the first array again to draw the model, during this loop I read also the height from the second array and put it as a variable in the model drawing process.

Now I’m only facing this parallax problem. @Hyper Sonic: thanks for the link to the Terrain-LOD-tiles. As I zoom in I should get LOD=17. As far as I understand I should set this value to the following code line: var promise = Cesium.sampleTerrain(terrainProvider, 17, positions); However, it accepts only the value “14” for the level (and not 17), with all values over 14 I get “undefined” back. Since I need quite a near-to-ground zoom level the moving around of the objects is too strong in my case.

Another interesting fact is that the camera sets automatically so that it looks down at the given position instead of using the defined pitch:

var center = Cesium.Cartesian3.fromDegrees(lat, long, 500);
var heading = Cesium.Math.toRadians(90.0);
var pitch = Cesium.Math.toRadians(-55.0);
var range = 1000.0;