CesiumJS how to keep the zoom level of a Cesium.Viewer when the viewer.trackedEntity is changed - cesiumjs

I have 2 different Cesium.Viewer instances, I want sync the 2 viewers when user zoom either one of them.
How to do that?
update:
In my app, I have 2 different Cesium.Viewer instances. But for this question here, I think it has nothing to do with the number of Cesium.Viewer. So I updated the question as below:
I have 2 planes flying on a Cesium.Viewer as shown in the attached screenshot, one is on the red course (ref as red plane) and the other is on the red course (ref as red plane).
Step-1: I tracked the yellow plane by double click it, then it looks like pic-1;
Step-2: I zoom out it to pic-2;
Step-3: I changed to track the red plane by double click it, and it looks like pic-3;
Step-4: I zoom out it to pic-4;
Whenever I changed the tracked entity (as step-3), I need to zoom out it manually again.
So, my question is how to keep the zoom level when changing the tracked entity?

Save current camera.view.scene
Detect moment when trackedEntity changed
Restore camera.view.scene
To save and restore you can use this code as an example:
var viewer = new Cesium.Viewer('cesiumContainer');
var savedView = {};
Cesium.CzmlDataSource.load('../../SampleData/simple.czml').then(function(dataSource) {
viewer.dataSources.add(dataSource);
Sandcastle.addToolbarButton('Save View', function() {
savedView.offset = viewer.scene.camera.position.clone();
});
Sandcastle.addToolbarButton('Restore View', function() {
viewer.trackedEntity._viewFrom._value = savedView.offset;
});
});
Note: viewFrom param should be in source czml for track. Smth like:
"viewFrom": {
"cartesian": [
-2080,
-1715,
779
]
},

Related

Pan map with mouse wheel click instead of left click

The default behavior of panning the map is via left click + drag I would like to change that behavior to be with wheel click + drag.
Is this possible?
That's handled by the ScreenSpaceCameraController, and is configurable. For example:
const viewer = new Cesium.Viewer("cesiumContainer");
// for 3D mode
viewer.scene.screenSpaceCameraController.rotateEventTypes = Cesium.CameraEventType.MIDDLE_DRAG;
// for 2D mode
viewer.scene.screenSpaceCameraController.translateEventTypes = Cesium.CameraEventType.MIDDLE_DRAG;
// remove MIDDLE_DRAG from the top of the tiltEventTypes.
viewer.scene.screenSpaceCameraController.tiltEventTypes.shift();
That last command above uses Array.shift to remove the first element of the tiltEventTypes array. The default value of this array (for many versions of Cesium) is shown here:
this.tiltEventTypes = [
CameraEventType.MIDDLE_DRAG,
CameraEventType.PINCH,
{
eventType: CameraEventType.LEFT_DRAG,
modifier: KeyboardEventModifier.CTRL,
},
{
eventType: CameraEventType.RIGHT_DRAG,
modifier: KeyboardEventModifier.CTRL,
},
];
This is showing us that one can still issue "tilt" events even after shifting away the MIDDLE_DRAG entry. We can CTRL-left-drag, for example, to get the same action.

Can i use docking panel in Autodesk forge for both viewers?

Example on Photo
I have two viewers to compare models. I created a docking panel for properties, and I want this panel to float in two viewers. Is it possible and who will tell you how to do it?
There are two options:
Option1:
Use the 'Autodesk.SplitScreen' extension, which will render up to 4 regions. You load it like this..
loadExtension('Autodesk.SplitScreen');
This simple extension can set up to four cameras, and render four regions. By default, it's just two (left and right). Here's the source code for how the core of it works, just in case you want to write your own...
https://autodeskviewer.com/viewers/latest/extensions/SplitScreen/SplitScreen.js
this.renderScenePart = function (scene) {
// Left
if (shouldRenderForViewport[0]) {
this.renderer.setViewport(0, vpVertStart, vpWidth, vpHeight);
this.context.renderScenePart.apply(this.context, arguments);
}
// Right
if (shouldRenderForViewport[1]) {
this.renderer.setViewport(vpWidth, vpVertStart, vpWidth, vpHeight);
this.context.renderScenePart.apply(this.context, arguments);
}
// Bottom left
if (shouldRenderForViewport[2]) {
this.renderer.setViewport(0, 0, vpWidth, vpHeight);
this.context.renderScenePart.apply(this.context, arguments);
}
// Bottom right
if (shouldRenderForViewport[3]) {
this.renderer.setViewport(vpWidth, 0, vpWidth, vpHeight);
this.context.renderScenePart.apply(this.context, arguments);
}
this.renderer.setViewport(0, 0, this.width, this.height);
this.renderer.enableViewportOnOffscreenTargets(false);
Option2:
For something more advanced, and specific to just 2D, you could also try the 'Autodesk.Viewing.PixelCompare' extension. Here's a blog post with much more detail and a demo...
BLOG: https://forge.autodesk.com/blog/compare-two-2d-documents-using-forge-viewer
ok, two more options:
option A:
if the second image is 'static'... why not just take a 'screenshot' and place it in the right side panel?
You can use the viewer.getScreenShot() command to retrieve a PNG blog, and paint it into the canvas.
// Get the full image
viewer.getScreenShot(viewer.container.clientWidth, viewer.container.clientHeight, function (blobURL) {
screenshot.src = blobURL;
});
For more details on drawing images to a canvas, see here: https://forge.autodesk.com/blog/screenshot-markups
option B:
if the two panels can be independently controlled, then perhaps try to sync the camera state, with some kind of button press (or action).
with the help of these blog articles:
https://forge.autodesk.com/blog/map-forge-viewer-camera-back-navisworks
Get the Camera position and restore it in Forge Viewer for a virtual visit
for example:
viewer.getState();
viewer.restoreState();
or restore camera position using navigation object:
const nav = this.navigation;
nav.getTarget();
nav.getPosition();
nav.getCameraUpVector();

Set instanceTree to a custom node in Forge 3D viewer

Lets say I'm working with a 3D file which is the combination of one Architectural model and one Structural model.
The instance tree or Model Browser looks like this
root/
Arch/
Level 01/
Level 02/
...
Str/
Level 01/
Level 02/
...
I want to display only the Level 01.
So I:
Followed the steps in the Viewer tutorial
Add an event listener to both Autodesk.Viewing.GEOMETRY_LOADED_EVENT & Autodesk.Viewing.OBJECT_TREE_CREATED_EVENT
When the 2 are fired, I use the code in this article to display only the Level 01 without ghosting.
I have 2 problem with this approach
I have to wait until the entire model is loaded before I can filter the level
After filtering the level, if I click on Model Browser, I can still see the entire model structure (but with everything as hidden except Level 01). How can I set the instance tree to only have what's below?
root/
Arch/
Level 01/
Str/
Level 01/
EDIT
At what point am I supposed to override the shouldInclude() function?
I've tried this and put a breakpoint but it seems it never gets called... I also tried to move it around but in vain.
const start = Date.now();
Autodesk.Viewing.UI.ModelStructurePanel.shouldInclude = (node) => {
Logger.log(node);
return true;
};
Autodesk.Viewing.Initializer(options, () => {
Logger.log(`Viewer initialized in ${Date.now() - start}ms`);
const config = {};
// prettier-ignore
Autodesk.Viewing.theExtensionManager.registerExtension('MyAwesomeExtension', MyAwesomeExtension);
viewerApp = new Autodesk.Viewing.ViewingApplication('MyViewerDiv');
viewerApp.registerViewer(viewerApp.k3D, Autodesk.Viewing.Private.GuiViewer3D, config);
loadDocumentStart = Date.now();
// prettier-ignore
viewerApp.loadDocument(documentId, onDocumentLoadSuccess, onDocumentLoadFailure);
});
Regarding #1: the object tree is stored in the file's internal database which - for performance reasons - is only loaded after the actual geometry.
Regarding #2: you can subclass the ModelStructurePanel class and add your own behavior, for example, by overriding the ModelStructurePanel#shouldInclude method.
Since I wasn't able to understand how to use ModelStructurePanel, I overrode Autodesk.Viewing.ViewingApplication.selectItem to only modify the options which are either passed to loadDocumentNode or startWithDocumentNode as below:
const options = {
ids: leafIDs.length > 0 ? leafIDs : null, // changed this line
acmSessionId: this.myDocument.acmSessionId,
loadOptions,
useConsolidation: this.options.useConsolidation,
consolidationMemoryLimit: this.options.consolidationMemoryLimit || 100 * 1024 * 1024, // 100 MB
};
With leafIDs being an array of objectIDs to display. I was able to build it by:
querying the ModelDerivativeAPI using GET :urn/metadata/:guid
going through the tree to find the ids which I am interested in.
There's probably a more elegant way to do this but that's the best I could do so far.

backbone.js Image uploading

I'm using nodecellar to get a better understanding of backbone.js and I've come across a problem which was answered in great detail in a similar question, however, the answer doesn't seem to be working for me:
What i'm trying to do.
Using Nodecellars wineview, i'm trying to utilise the drag and drop feature, to upload an image. I've made a note of a previous very well answered question which basically states that you have to disable the default behaviour of on dragover, so I have the following in my wine view:
events: {
"change" : "change",
"click .save" : "beforeSave",
"click .delete" : "deleteWine",
"drop #profile" : "dropHandler",
"dragover #profile" : "dragover",
'dragenter #profile' : 'alertMe'
},
then my dragover event looks like this:
dragover: function(event){
console.log('drag over event called');
event.preventDefault();
},
This is fine and working as the console log is called when it fires. and the draghandler looks like this:
dropHandler: function (event) {
event.stopPropagation();
event.preventDefault();
var e = event.originalEvent;
e.dataTransfer.dropEffect = 'copy';
this.pictureFile = e.dataTransfer.files[0];
// Read the image file from the local file system and display it in the img tag
var reader = new FileReader();
reader.onloadend = function () {
$('#profile').attr('src', reader.result);
$('#picText').html('Picture added, select save to complete changes.')
};
reader.readAsDataURL(this.pictureFile);
}
The problem
The code works, however it doesn't upload the image or save the details in the model to the database. so once I move away then select the wine again the image is back to what it was originally.
I've done some research into the HTML5 fileReader api, but there isn't much on how it uploads, or where it uploads to.
And this is where I am now, I'm looking to you guys for suggestions on how to ensure the model saves the image url, and the image is uploaded to the pics folder.
How is the best way to go about this.
Is the HTML 5 fileReader API the best option to go with?
Thank you for your feedback.
Jay
You are missing in your function dropHandler the call to your model to set the property for the image you want to save.

Google Maps V3 Infobox undefined on polygons

I have a spoke in my wheels and I am not sure how to sort this out. I have been struggling with it for a couple days and it isn't like a normal infobox as it is not set to a marker rather a polygon which is something new for me. I have polygons that display with data from an XML file and they show up fine. I have searched the web and got it to have the mouseover set up to where you mouseover a polygon the opacity changes and an infobox pops up. Problem is the infobox when it pop up shows "undefined" instead of the html I have set in it to display with data from the XML file.
Here is a link to the test map for example.
http://www.mesquiteweather.net/googlemap_poly.html
Here is a link to the XML file where I am just trying to show the elements events and expires in the info box.
http://www.mesquiteweather.net/xml/warnings_test.xml
This is the code I am working with to create the infoboxes and mouseover events
function attachPolygonInfoWindow(polygon, html, event, expires)
{
var html = "<strong>" + event + "</strong>";
eventWarnings.infoWindow = new google.maps.InfoWindow({content: html});
google.maps.event.addListener(eventWarnings, 'mouseover', function(e) {
var latLng = e.latLng;
this.setOptions({fillOpacity:80});
polygon.infoWindow.setPosition(latLng);
polygon.infoWindow.open(map);
});
google.maps.event.addListener(eventWarnings, 'mouseout', function() {
this.setOptions({fillOpacity:0.35});
polygon.infoWindow.close();
});
}
var polygon = new google.maps.Polygon(/* omitted for brevity */);
attachPolygonInfoWindow(eventWarnings);
eventWarnings.setMap(map);
}
});
I am pretty sure it is something easy I am overlooking but I haven't been able to find anything that pertains to my issue. I am just lucky I got the infobox to show at all as I have learned it's tricky since polygons don't have a true center and they are not set up like you would with a marker which I can handle.
If anyone has any suggestions please let me know.
-Thanks
You defined your attachPolygonInfoWindow function with 4 argument, but only provide one when you call it:
// definition
function attachPolygonInfoWindow(polygon, html, event, expires)
...
// call
attachPolygonInfoWindow(eventWarnings);
Probably you want (I don't see the html or expires parameters being used):
attachPolygonInfoWindow(eventWarnings, "", event, null);
The other option would be to change the definition to:
// definition
function attachPolygonInfoWindow(polygon, event, expires)
and the call to (assuming you are going to use "expires" for something):
attachPolygonInfoWindow(eventWarnings, event, expires);
As it doesn't look like you need to pass in that parameter (event is serving the function that I would expect it to serve).
Also, FYI, you have a "hanging comma" in your alertColors.js which make IE unhappy...
example