2d scale implementation for Legend?

In the well-constrained 2D case, is there a canned implementation for a scale indicator, such as a printed map legend would typically provide? (I want to provide measurement context for the user, similar to miles/kilometers per inch – not zoom levels.)

Arguably, this might vary by projection. However, I am not seeking cartographic correctness, just simply an approximate indication of scale as users interact with the zoom controls.

I would like to see something similar in the 2.5D and 3D models, but I understand that the differing constraints make the problem more challenging.

Thank you,

-Kirk

Hi Kirk,

Cesium doesn’t have any sort of built-in scale indicator, but you can grab the one we wrote for National Map. The code is here (Apache 2.0 license):

https://github.com/NICTA/nationalmap/blob/phil-ui/src/ViewModels/DistanceLegendViewModel.js

https://github.com/NICTA/nationalmap/blob/phil-ui/src/Views/DistanceLegend.html

https://github.com/NICTA/nationalmap/blob/phil-ui/src/Styles/DistanceLegend.less

You can see what it looks like here:

http://nationalmap.research.nicta.com.au/demo/

Kevin

I should mention that this works in 3D only. Adapting it for 2D and Columbus View is probably not too difficult.

Kevin,

Thank you very much for sharing your implementation!

-Kirk

Kevin,

Did you move this repo? I am interested in this.

Thanks!

Jay

I see that the 2D scale display refers to the terrain between the camera and Earth’s center, which is matches the view if the camera pitch is at or near -90 degrees. This is probably how most 2D scales operate, as most people don’t tilt the view I suppose.

What would be neat is when the camera is tilted have a 2D scale on all 4 corners of the screen and one in the middle. If that’s going overboard, perhaps just the standard ‘below the camera’ scale and one for the center of the screen when tilted.

Hi Jay,

No, the repo is in the same place, but the code has been merged to master and the phil-ui branch has been deleted. If it’s not obvious how to update the URLs, let me know and I’ll send them through next time I’m on a real computer.

Kevin

https://github.com/NICTA/nationalmap/blob/master/src/ViewModels/DistanceLegendViewModel.js

https://github.com/NICTA/nationalmap/blob/master/src/Views/DistanceLegend.html

https://github.com/NICTA/nationalmap/blob/master/src/Styles/DistanceLegend.less

Awesome, instead of adding a function yourself to calculate the shortest distance along the curved surface (such as from http://www.movable-type.co.uk/scripts/latlong.html ) There’s a Cesium function that’ll calculate this for you:

geodesic.setEndPoints(leftCartographic, rightCartographic);

var pixelDistance = geodesic.surfaceDistance;

I see that I was wrong, the 2 rays cast are at the bottom center of the screen. I’m surprised they are only 1 pixel apart, but I suppose that is sufficient. Most of the time the bottom center of the screen shows the Earth, but if you zoom way out and pitch way down the rays won’t intersect the Earth. However, this is a rare case from which I doubt many will be using to measure.

Right, National Map just hides the scale widget when the bottom center two pixels don’t hit anything. It’s good enough for our purposes.

Hi Kevin,

Can you please tell me what changes I need to make to work it in 2D and 2.5D?

Thanks,
Gowtham.

For 2D screen width in meters is:

viewer.scene.camera.frustum.right-viewer.scene.camera.frustum.left

screen height in meters is:

viewer.scene.camera.frustum.top-viewer.scene.camera.frustum.bottom

To determine pixelDistance divide screen width in meters by viewer.canvas.clientWidth

For CV(‘2.5D’) it’s mostly the same as 3D, except the part where you determine the distance between leftPosition and rightPosition. Since the world is flat and not curved it’s a simple straight line distance computation rather than curved distance computation. So I believe you’d just have to change these 4 lines

var leftCartographic = globe.ellipsoid.cartesianToCartographic(leftPosition);

var rightCartographic = globe.ellipsoid.cartesianToCartographic(rightPosition);

geodesic.setEndPoints(leftCartographic, rightCartographic);

var pixelDistance = geodesic.surfaceDistance;

``

Determine the distance vector by subtracting leftPosition and rightPosition, and pixelDistance is the magnitude of that vector.

Add viewer.scene.mode checks to determine which method to use.

On second thought, although that will get you the actual displayed distance, flat maps of Earth are warped. The Equator on a flat map is 40 Mega Meters which is the circumference. However, the Artic circle’s circumference is about 16 Mega Meters yet it is stretched out at 40 Mega Meters. So the horizontal warp depends on the latitude. Vertical is warped in a different manner. So just use the same code as 3D. CV represents the curved Earth although it is displayed in a warped fashion.

Hi Kevin,
I am able to implement navigation controls and compass for 3D but it's not working for 2D and 2.5D. I need your help in implementing it for 2D and 2.5 D. Did you implemented it for 2D and 2.5D also? If not can you guide me on this?
Thanks in advance,
Gowtham.

Hi Gowtham,

No, sorry, I didn’t implement it for 2D and 2.5D. In the main project I work on, we use Leaflet instead of Cesium’s 2D.

I’m afraid I can’t tell you offhand how to make it work. I would have to figure it out myself.

Kevin

Getting positions in Columbus

pickEllipsoid https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Camera.js#L1920
pickMapColumbusView https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Camera.js#L1896

This is similar to what I had here when picking the 2D plane.
https://groups.google.com/d/msg/cesium-dev/37Hq0Hj8HSs/NHO5T959moAJ

However regular and WC coordinates don’t match in Columbus mode, WC is ‘swizzled’

https://groups.google.com/d/msg/cesium-dev/SNi-HLT4NS4/ANxwf-6OhUoJ

And getPickRayPerspective https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Camera.js#L1949 uses the swizzled WC coordinates, which produces a swizzled ray.

To make things less confusing you can un-swizzle the ray

function unSwizzle(cart)
{
var temp=cart.z;
cart.z=cart.x; //X to Z
cart.x=cart.y; //Y to X
cart.y=temp; //Z to Y
}
ray.origin=unSwizzle(ray.origin);
ray.direction=unSwizzle(ray.direction);

``

So first get 2 rays one pixel apart on the bottom of the screen using camera.getPickRay , un-swizzle them, then

//get point on ground in Cartesian (unswizzled)
var CC3=Cesium.Cartesian3;
var steps=camera.position.z/(-myRay.direction.z);
var vec=new CC3();desCarte=new CC3();
CC3.multiplyByScalar(myRay.direction,steps,vec);
CC3.add(myRay.position,vec,desCarte);

``

This next part is similar to this function

https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Core/GeographicProjection.js#L96

Which I think is called here https://github.com/AnalyticalGraphicsInc/cesium/blob/master/Source/Scene/Camera.js#L1901

//Cartesian to Cartographic (for Columbus and 2D unswizzled)
function yToLat(y)
{
var neg=1;if(y<0){neg=-1;}y=Math.abs(y);
var halfheight = Math.PI * 6378137 / 2; //width/2/2;
var ratio = y / halfheight;
return ratio * (Math.PI/2) * neg;
}
desCarto = new Cesium.Cartographic();
desCarto.longitude=desCarte.x / 6378137;
desCarto.latitude=yToLat(desCarte.y);
desCarto.height=desCarte.z;

``

Once you have both of your Cartographic points you can figure the distance (this is from https://github.com/NICTA/nationalmap/blob/master/src/ViewModels/DistanceLegendViewModel.js )
var leftCartographic = globe.ellipsoid.cartesianToCartographic(leftPosition);
var rightCartographic = globe.ellipsoid.cartesianToCartographic(rightPosition);
geodesic.setEndPoints(leftCartographic, rightCartographic);
var pixelDistance = geodesic.surfaceDistance;

``

https://github.com/NICTA/nationalmap/blob/master/src/ViewModels/DistanceLegendViewModel.js is no longer online however.

Hi Hyper,

We reorganized things in National Map quite a bit over the past couple of days. The code you’re looking for is now here:

https://github.com/TerriaJS/terriajs/blob/master/lib/ViewModels/DistanceLegendViewModel.js

Kevin

Thanks for the updated link Kevin! From DistanceLegendViewModel.js the 2 rays are 1 pixel apart
var left = scene.camera.getPickRay(new Cartesian2((width / 2) | 0, height - 1));
var right = scene.camera.getPickRay(new Cartesian2(1 + (width / 2) | 0, height - 1));

``

This should work well for Columbus as well as 3D.

Hi Kevin,
As per the implementation of scale legend in DistanceLegendViewModel.js geodesic.surfaceDistance is the actual distance between two points in cesium map. For what purpose you are using distances array and you are taking values like 1,2,3,5.. 10,20,30,50.. like that. Could you please explain me about this?

Thanks,
Gowtham.

https://github.com/TerriaJS/terriajs/blob/master/lib/ViewModels/DistanceLegendViewModel.js#L122
// Find the first distance that makes the scale bar less than 100 pixels.

You can check it out at http://nationalmap.nicta.com.au/

The legend bar is on the bottom right.