Cesium - drawing polygon using camera Lat-Lon-Alt positions - javascript

This question is related to these two:
Cesium how to scale a polygon to match Lat-Lon positions while zoom-in/zoom-out
Cesium - using camera to scale a polygon to match Lat-Lon positions while zoom-in/zoom-out
The sample code I am following to get lat-lon-alt positions from the camera is located in the gold standard that appears to be baked into the existing camera controller. With this code I can retrieve lat-lon-alt positions from the distance of the camera to get values that are almost exact to the original lat-lon position selected and a height above the surface of the earth. Perfect!
All examples and documentation show polygon creation using degrees or points from degrees.
Now what? Maybe I'm missing something but the intent I thought was to be able to create the polygon using specific x, y, z coordinates so the polygon would "stick" to the top of my house on zoom-in, zoom-out, and camera movement. Now that I have those values, what is the secret to drawing the polygon with those values?
FYI, these are the value I currently have:
=========================NEW INFORMATION===========================
The code for the redPolygon works:
var redPolygon = viewer.entities.add({
name : 'Red polygon on surface',
polygon : {
hierarchy : Cesium.Cartesian3.fromDegreesArray([-115.0, 37.0,
-115.0, 32.0,
-102.0, 31.0,
-102.0, 35.0,
-102.0, 35.0]),
material : Cesium.Color.RED
}
});
viewer.flyTo(redPolygon);
The code for the bluePolygon does not work:
var bluePolygon = viewer.entities.add({
name : 'Blue polygon on surface',
polygon : {
//hierarchy: collection.latlonalt,
hierarchy: Cesium.Cartesian3.fromArray(collection.latlonalt),
material : Cesium.Color.BLUE
}
});
viewer.flyTo(bluePolygon);
If I use hierarchy: collection.latlonalt, I receive the following error:
So I changed the code to hierarchy: Cesium.Cartesian3.fromArray(collection.latlonalt), where collection.latlonalt is my Cartesian3 array:
But nothing gets drawn. No errors. This is what I see in the console:
Just for test, I tried adding a z position to the redPolygon and changing .fromDegreesArray to .fromArray like this:
var redPolygon = viewer.entities.add({
name : 'Red polygon on surface',
polygon : {
hierarchy : Cesium.Cartesian3.fromArray([-115.0, 37.0, 10.0,
-115.0, 32.0, 10.0,
-102.0, 31.0, 10.0,
-102.0, 35.0, 10.0,
-102.0, 35.0, 10.0]),
material : Cesium.Color.RED
}
});
viewer.flyTo(redPolygon);
That didn't work either.

Cesium has helper functions like Cartesian3.fromDegreesArray that are used by the Polygon Demo, but, these helper functions are not needed now that you've got your hands on actual Cartesian3 values.
For example, the polygon demo code looks like this:
var redPolygon = viewer.entities.add({
name : 'Red polygon on surface',
polygon : {
hierarchy : Cesium.Cartesian3.fromDegreesArray([-115.0, 37.0,
-115.0, 32.0,
-107.0, 33.0,
-102.0, 31.0,
-102.0, 35.0]),
material : Cesium.Color.RED
}
});
In the above code, fromDegreesArray in this case just takes a list of 5 lot/lan value pairs, and converts them into a JavaScript array of 5 instances of the Cartesian3 class. This array of 5 Cartesian3s is then stored as the value of hierarchy in the polygon definition. If you inspect that definition at runtime, you'll find the original lon/lat values have been discarded, replaced by the actual Cartesian3s, thanks to the helper function.
So in your code, you'll need an array of Cartesian3s that the user has clicked on thus far. This starts as the empty array, and you'll need to gather at least three clicks, converting each click into a Cartesian3 as you've shown works in your question above, and push that value into the array. Once the array has accumulated 3 or more clicks, you can then pass that array as the hierarchy field of the polygon definition.
In this manner, you've avoided calling fromDegreesArray because your click handler is doing the more detailed work of gathering an exact Cartesian position per click. This gathering has to happen at the time of each click, in case the camera is moved between clicks. So, the array "in progress" has to survive between clicks, until all the clicks have been gathered and a polygon can be created.
EDIT: Here's an example of the code structure I'm trying to describe. I don't show the actual click handlers here, since you seem to have Cartesian3 values coming out of your mouse clicks already. Instead, I show three such values being used to create a polygon.
var viewer = new Cesium.Viewer('cesiumContainer');
// Create an empty array of click positions at the start.
var clickPositions = [];
// When the first mouse click is received, convert to Cartesian3, and push it into the array.
var click1 = new Cesium.Cartesian3(-2155350.2, -4622163.4, 3817393.1);
clickPositions.push(click1);
// Later, more mouse clicks are received and pushed into the array.
var click2 = new Cesium.Cartesian3(-2288079.8, -4906803.1, 3360431.4);
clickPositions.push(click2);
var click3 = new Cesium.Cartesian3(-1087466.8, -5116129.4, 3637866.9);
clickPositions.push(click3);
// Finally, draw the polygon.
var redPolygon = viewer.entities.add({
name : 'Red polygon on surface',
polygon : {
hierarchy : clickPositions,
material : Cesium.Color.RED
}
});
Notice nothing happens to clickPositions when it's assigned to hierarchy. The array of Cartesian3 values is already in the form needed by Cesium here.

Related

Finding whether the coordinate is within polygon

I want to know if the given coordinates (lat & lon) is within given 4 coordinates.
i.e., If i have the lat,lon of A,B,C,D and E and i wanted to check if E lies within A,B,C and E.
How can i do that ?
I tried to see this example from Google Map Coordinates LatLngBound class - contains.
The example i can see is like
var bounds = new google.maps.LatLngBounds(
new google.maps.LatLng(54.69726685890506,-2.7379201682812226),
new google.maps.LatLng(55.38942944437183, -1.2456105979687226)
);
var center = bounds.getCenter(); // still returns (55.04334815163844, -1.9917653831249726)
var x = bounds.contains(center); // now returns true
It has only 2 coordinates, like A&B. Hows that possible without having C&D's coordinates ?
It's because this is using LatLngBounds which creates a rectangle, not a polygon. If you have two corners of a box, the other two corners can be assumed. You just need to specify the opposite corners. In your example you just need to send either A and D or B and C as a pair, the other two can be ignored. If you need a polygon then you want to create a polygon and then get determine if it contains the point with google.maps.geometry.poly.containsLocation(point, polygon).
containsLocation() Documentation here.
Full example here.

Add Points to a Point Cloud with User Mouse Clicks

I'm using Three.js to render point cloud data retrieved from a server.
For each data set, I loop over the data points and create a Three.js Vector3 object with x, y & z values corresponding to each data point. I push each of these vertices onto a list which I then pass into the vertices prop of my geometry component within my points component.
render() {
this.pointCloudVertices = [];
if (this.props.points) {
const points = this.props.points
for (let i = 0; i < points.x.length; i++) {
const vertex = new THREE.Vector3();
vertex.x = points.x[i]
vertex.y = points.y[i]
vertex.z = points.z[i]
this.pointCloudVertices.push(vertex);
}
}
return (<points>
<geometry vertices={this.pointCloudVertices}/>
<pointsMaterial
color={ (Math.floor(Math.random()*16777215)) }
size={ 0.2 }
/>
</points>);
}
https://github.com/caseysiebel/pc-client/blob/master/src/components/PointCloud.js
I'd like the user to be able to use their mouse to add points to another point cloud (points component) by clicking inside the canvas.
I found a lot of resources pointing to the Three.js' Raycaster, but this tool seems to be more for selecting out objects already in the canvas. In my case I'd like the user to be able to click on and area in the canvas not occupied by an object, have the client work out the x, y & z coordinates of that click and then add a vertex, with those x/y/z values, to a points component (likely empty until the user adds points via this modality).
I'm a little confused as to how I will convert 2D mouse events into a 3D vertex value. If anyone knows any good resources on this subject I'd love to check them out.
With THREE.Raycaster(), I see several solutions:
1. Use the .at() method of the .ray property. Like this:
raycaster.ray.at(100 + Math.random() * 150, rndPoint);
Here you can set the constraints for the distance from the origin of the ray, and it will look like this from your original camera:
and how it will look like from aside:
jsfiddle example. You can switch the lines off there.
2. Use the .intersectObjects() method. Where intersecting objects are planes of constraints. For example, we have planes in the form of a cube. When we cast a ray through them, we always intersect two planes, and the array of intersectec objects will be sorted by distance from the origin of the ray. Thus the first element in this array will be the closest plane. So, when we know it, we can take their points of intersection and sub point1 from point2, getting a new vector (its length and direction). And then we'll just set a point at a random place along the vector from point1 to point2:
intersected = raycaster.intersectObjects(planes.children);
if (intersected.length > 0){
var point1 = intersected[0].point;
var point2 = intersected[1].point;
var diff = point2.clone().sub(point1);
var diffLength = diff.length();
rndPoint = point1.clone().addScaledVector(diff.normalize(), Math.random() * diffLength);
. . .
}
It will look like this from the front camera:
and from aside:
jsfiddle example. Lines are switchable here too.
Or you can use THREE.Raycaster() with THREE.OrthographicCamera(). Which is simplier )

OpenLayers : 4326 ol.proj.transform not working

Issue
When I click on the map I want to transform the coordinates to format of 4326. When I do this it seems the Latitude part of the array transforms fine, but the Longitude part is incorrect and not valid.
Code
When I click on the Map, the SingleClick event fires, I then get the coordinates of where the user clicked:
Example of pPointClicked = [-40364190.03366159, 7054830.416117247];
session.Map.on('singleclick', function (e) {
var pPointClicked = e.coordinate;
});
I try and transform these coordinates into 4326 by using the code below:
Example of coord4326 = [-362.59768838343064, 53.38659640004323];
session.Map.on('singleclick', function (e) {
var pPointClicked = e.coordinate;
var coord4326 = ol.proj.transform(pPointClicked, 'EPSG:3857', 'EPSG:4326');
});
As you can see the first value in the transformed variable is -362.59768838343064 which is incorrect? Does anyone know why this is happening.
The transformation seems to be working. Here's what I think is happening: you are not within the "original extent" of your map, i.e. you panned west and wrapped the entire world at least twice.
Try zooming out completely, then pan at the "same" location to the east twice, then click again. You should have the coordinate you're looking for.
Here's an other tip: the world extent, in EPSG:3857, is:
[
-20037508.342789244,
-20037508.342789244,
20037508.342789244,
20037508.342789244
]
[-40364190.03366159, 7054830.416117247] is out of that extent, but if you pan to wrap the world twice, you should get: 289173.348083102, 7054830.416117247], which is within the extent.

Imported meshes can't be picked deterministically

I've got grid of cylinder meshes created simply by
var tile = BABYLON.MeshBuilder.CreateCylinder("tile-" + i, { tessellation: 6, height: 0.1 }, scene);
then I have following event callback
window.addEventListener("click", function (evt) {
// try to pick an object
var pickResult = scene.pick(evt.clientX, evt.clientY);
if (pickResult.pickedMesh != null){
alert(pickResult.pickedMesh.name)
});
Then mouse-click on one of tiles raises message box with correct tile name.
When I add some new meshes (3D model inside .babylon file) by
var house;
BABYLON.SceneLoader.ImportMesh("", "../Content/"
, "house.babylon"
, scene
, function (newMeshes)
{ house = newMeshes[0]; });
For better imagination it's texture of house created from four different meshes which is placed over grid of cylinder tiles.
It's displayed fine but when mouse-click it too much often behave as it would totally ignore there is such a mesh and so pickResult.pickedMesh is either null or pickResult.pickedMesh.name points to tile underlaying my imported mesh in point I've clicked.
Just approximately 5% of mesh area corresponds properly to mouse-clicks (let's say in middle of roof, in middle of walls).
I've tried playing with setting some virtual (hidden) house.parent mesh for that which would not be created by importing meshes but seems as dead end.
Are you aware about some way how enforce that scene.pick(evt.clientX, evt.clientY); would respect mesh hierarchy and would consider all visible parts of overlaying texture?
Just for completeness I'm working with middle part of this 3D model (removed left and right house from that).
EDIT: Demo on BabylonJS playground
you could try change
var pickResult = scene.pick(evt.clientX, evt.clientY);
to
var pickResult = scene.pick(scene.pointerX, scene.pointerY);
as evt corresponds to whole page.

Cesium how to 'drape' a polygon or line onto terrain surface

So, I'm using cesium and I want to add a polygon or line to represent a property boundary on a terrain surface.
My polygon works fine on the flat/Ellipsoid surface, unfortunately however the polygon doesn't automagically drape over the surface when the terrain layer is shown.
Fair enough, I don't actually have the z/height values - so I'm using the sampleTerrain.js promise method to interpolate the height values based on the terrain. This part works fine, I get my height values. But then what?
I've tried creating a polygon entity with my height-laden positions, but to no avail - it just ignores the height values. When I read the docs, I can really see any reference to height values being ingested - all the "positions" array are two dimensional?
The only reference to height values being considered is in the PolygonOutlineGeometry, which has a promising looking property called perPositionHeight.
This is essentially what I want - I don't want to set the height of the whole poly, I want every points height value to be used..
Here's one of my unsuccessful attempts:
Entity/Polygon:
var entity = viewer.entities.add({
polygon : {
hierarchy : cartesianPositions, //array of positions with z values
outline : true,
outlineColor : Cesium.Color.RED,
outlineWidth : 9,
material : Cesium.Color.BLUE.withAlpha(0.0),
}
});
Bottom line: I just want a polygon or polyline entity that sits nicely on the surface of the terrain.
EDIT:
Using the Orange Polygon example in the comments of the accepted answer combined with sampleTerrain.js, I've been able to simulate 'draping' a polygon onto terrain, with a list of positions that did not have z values, here's a crude example:
var positions = []; // xy position array
var cesiumTerrainProvider = new Cesium.CesiumTerrainProvider({
url : '//assets.agi.com/stk-terrain/world'
});
viewer.terrainProvider = cesiumTerrainProvider;
// go off and sample the terrain layer to get interpolated z values for each position..
var promise = Cesium.sampleTerrain(cesiumTerrainProvider, 11, positions);
Cesium.when(promise, function(updatedPositions) {
var cartesianPositions = Cesium.Ellipsoid.WGS84.cartographicArrayToCartesianArray(updatedPositions);
var entity = viewer.entities.add({
polygon : {
hierarchy : cartesianPositions,
outline : true,
outlineColor : Cesium.Color.RED,
outlineWidth : 9,
perPositionHeight: true,
material : Cesium.Color.BLUE.withAlpha(0.0),
}
});
viewer.flyTo(entity);
});
As of version 1.13 cesium now supports GroundPrimitives. They will drape over terrain.
It looks like this: http://cesiumjs.org/images/2015/09-01/groundPrimitives.gif
This the example Cesium gives:
var rectangleInstance = new Cesium.GeometryInstance({
geometry : new Cesium.RectangleGeometry({
rectangle : Cesium.Rectangle.fromDegrees(-140.0, 30.0, -100.0, 40.0)
}),
id : 'rectangle',
attributes : {
color : new Cesium.ColorGeometryInstanceAttribute(0.0, 1.0, 1.0, 0.5)
}
});
scene.primitives.add(new Cesium.GroundPrimitive({
geometryInstance : rectangleInstance
}));
Cesium does not support vector data on terrain yet. It is being actively worked on and most features should start to appear in Cesium 1.10 which will be out on June 1st. Anything that doesn't make that release should be in 1.11 on July 1st.
For Polygons specifically, you can follow along with the GitHub pull request: https://github.com/AnalyticalGraphicsInc/cesium/pull/2618
For Billboards and Labels, check out: https://github.com/AnalyticalGraphicsInc/cesium/pull/2653
Polylines haven't been started yet but will be as soon as the above two are finished.

Categories

Resources