Background
I'm using Cesium to run a simulation while capturing images with Viewer.canvas.toDataURL. The simulation has some action in it, and I would like the map imagery to be completely loaded when the action starts. However, for the first few renders, the imagery is always very low detail.
I only want to capture images at 15fps, so I render at most 15fps, for the sake of efficiency. The problem is that imagery starts low-detail and only updates when I render. It takes around 75 renders for the high-detail imagery to load, meaning that the first ~5 seconds of output images are rendered with low-detail imagery.
Question
I would like to reduce the number of output images with low-detail imagery to less than 15, ideally 0.
How can I
a) pre-load the imagery so that it's completely loaded by the time I start rendering
or
b) use the time in between renders to load the imagery, so that I minimize the number of frames with low-detail imagery?
Or maybe there is another solution I haven't thought of yet.
Things I've tried
1) I've tried restricting the area of the imagery provider, to load fewer images. This causes no discernible change, and in fact it does not cause any change to Viewer.imageryLayers._layers[0]._imageryProvider.rectangle.
var Viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: Cesium.BingMapsImageryProvider({
url: 'https://dev.virtualearth.net',
rectangle: Cesium.Rectangle.fromDegrees(
Params.Lon - 5, Params.Lat - 5, Params.Lon + 5, Params.Lat - 5)
})
2) I've tried raising the minimumLevel of the imagery provider, hoping that would prevent it from loading the low-detail imagery at all. Again, this doesn't fix the problem and Viewer.imageryLayers._layers[0]._imageryProvider.minimumLevel is unaffected, always 0.
var Viewer = new Cesium.Viewer('cesiumContainer', {
imageryProvider: Cesium.BingMapsImageryProvider({
url: 'https://dev.virtualearth.net',
minimumLevel: 15
})
});
Minimal working example
var Viewer = new Cesium.Viewer('cesiumContainer')
Viewer.useDefaultRenderLoop = false;
var CameraTarget = new Cesium.ConstantPositionProperty(
new Cesium.Cartesian3(
-1673357.9867530654,
-4597650.657420824,
4077993.9927027463
)
);
var CameraOffset = new Cesium.ConstantPositionProperty(
new Cesium.Cartesian3(20, 2, 15)
);
var RenderInterval = 1 / 15; // seconds
var TimeDelta = 0;
var LastRenderTime = 0;
function ScreenCapture()
{
console.log('capture');
}
function Render()
{
var CurrentTime = Viewer.clock.currentTime.secondsOfDay;
var TimeDelta = CurrentTime - LastRenderTime;
if (TimeDelta > RenderInterval)
{
Viewer.camera.lookAt(
CameraTarget.getValue(Viewer.clock.currentTime),
CameraOffset.getValue(Viewer.clock.currentTime)
);
Viewer.render();
LastRenderTime = CurrentTime;
}
else
{
Viewer.clock.tick();
}
Cesium.requestAnimationFrame(Render);
}
Cesium.requestAnimationFrame(Render);
Related
When loading text markups from a database, the text markups show up in a different size based on the current zoom of the viewer. How do I make text markups show at a static size regardless of zoom?
function saveFreeformMarkup(markup){
let markupObject = {
x: markup.position.x,
y: markup.position.y,
width: markup.size.x,
height:markup.size.y,
type: TEXT_MARKUP_TYPE,
text: $(`#freeText`).val(),
urn_id: urn[`id`],
active: ACTIVE
};
$.ajax({
... send markupObject to database ...
});
}
function loadSingleMarkup(markup, markupTool){
let MarkupsCore = Autodesk.Viewing.Extensions.Markups.Core;
let text = new MarkupsCore.MarkupText(markup.id + ID_INCREMENT, markupTool, markup.text);
markupTool.addMarkup(text);
text.setSize({ x: markup.x, y: markup.y}, markup.width, markup.height);
text.setText(markup.text);
text.updateStyle(true);
}
This is because there's an handler attached to the camera change event that adjusts the viewbox of the SVG per the updated bounds of the current view when navigation (scaling/panning) occurs.
To overcome this you can piggyback the onCameraChange handler of the MarkupCore extension (be sure to do this prior to the event binding to the upper chain that is before entering the edit mode) and apply scaling to the SVG based on the current camera pivot values and the ones recorded when you added the markups:
MarkupsCore.originalOnCameraChange = MarkupsCore.onCameraChange;
MarkupsCore.onCameraChange = function(event){
let scaleString = calculateScale(originalPivot, viewer.autocam.pivot);
this.svg.setAttribute('transform', scaleString);
this.originalOnCameraChange(event)
}
See here for details on SVG transform.
Will leave it up to you to implement the calculations or even a better approach to transform the markups in response to navigation.
I was able to fix the issue by changing the loadSingleMarkup() function to the following
const FONT_SIZE_SCALE = 90;
function loadSingleMarkup(markup, markupTool){
let MarkupsCore = Autodesk.Viewing.Extensions.Markups.Core;
let text = new MarkupsCore.MarkupText(markup.id + ID_INCREMENT, markupTool, markup.text);
markupTool.addMarkup(text);
text.setSize({ x: markup.x, y: markup.y}, markup.width, markup.height);
text.setText(markup.text);
text.style[`font-size`] = 12 / FONT_SIZE_SCALE;
text.updateStyle(true);
}
(adding text.style[`font-size`] = 12 / FONT_SIZE_SCALE;)
Has anyone attempted to do this? As far as I can tell from the documentation, there doesn't seem to be a built in function for achieving this. Does anyone know if this is possible? Is it, possibly, a feature that the authors might intend to add to the platform?
The ImageryLayer documentation shows how to control brightness, contrast, hue, saturation, and gamma correction.
To get a globe with a solid color, you can remove the imagery layers like so:
var viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false
});
var globe = viewer.scene.globe;
globe.imageryLayers.removeAll();
globe.baseColor = Cesium.Color.LIGHTSLATEGRAY;
A "minimalist map" (as you mention in the comments) is something you would probably need to get from a custom imagery provider. You may want to check out Stamen Maps for some examples of this. In particular, note their "Toner" map comes in a number of sub-varieties, any of which can be selected in Cesium.
For example, to try the "Toner Background" version, you would use:
var viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false,
imageryProvider: Cesium.createOpenStreetMapImageryProvider({
url : 'https://stamen-tiles.a.ssl.fastly.net/toner-background/',
credit : 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.'
})
});
EDIT: #EmmanuelBuckski (OP) took this idea and ran with it, mixing the above two techniques together to produce a result that looks really nice! Check it out:
var viewer = new Cesium.Viewer('cesiumContainer', {
baseLayerPicker: false
});
var globe = viewer.scene.globe;
globe.imageryLayers.removeAll();
globe.baseColor = Cesium.Color.fromCssColorString('#f3f3f3');
var tonerLayer = globe.imageryLayers.addImageryProvider(
Cesium.createOpenStreetMapImageryProvider({
url : 'https://stamen-tiles.a.ssl.fastly.net/toner-background/',
credit : 'Map tiles by Stamen Design, under CC BY 3.0. Data by OpenStreetMap, under CC BY SA.'
})
);
tonerLayer.alpha = 0.1;
What am trying to do is, load a google map on a page, then load up my markers one by on so the user is seeing as they become available. Am doing this on purpose because I want to show how much my site covers in a city. Now I have tried loading them before the page loads but that takes ages! (I have 600+ markers and growing)
I have the basic knowledge to display a map, load markers but this need a different approach which I need some help with. If any one could point me to a tutorial or some reference page to help me get started would be a huge help.
I am not sure what do you mean it takes ages. Who? Page to render? Map to render.
From a UI point of view ou should try a cluster markers id they are țoo close (there is a demo for v3 api)
You mean like the "too many markers" page from the documentation?
Load a few markers at a time with calls to setTimeout, for example:
//Global variable
var markersData = [];
markersData[0] = {
"lat": 45.0,
"lon": -91.0,
"name":"marker 0"
};
markersData[1] = {
"lat": 45.0,
"lon": -92.0,
"name":"marker 1"
};
//...etc
// Load 10 markers every 100 miliseconds
function load_10_markers(){
for (var n = 0 ; n < 10 ; n++) {
var markerData = markersData.shift();
if(markerData){
var marker = new google.maps.Marker({
position: new google.maps.LatLng(markerData.lat, markerData.lon),
title: markerData.name,
map: map
});
}
}
if(markersData.length){
window.setTimeout("load_10_markers()",100);
}
}
load_10_markers();
I am working on integrating Google Maps into a web application and I have a question of possibility.
I am using ASP .Net 4.0 as the basis for the code, but i suspect i will have to use JavaScript to achieve most of this, which is. Basically I want to display a bunch of markers on a map from Lat Long locations i have stored in a database, then have the user be able to draw a box on the map with the mouse, then get back the lat long of the four corners of the box.
If anyone knows how i could do this this would be of great help to me!
Thanks
The complexity of the Google Maps API has kept me scared for years. Recently I stumbled upon a jQuery plugin called GMap3 that does a lot of the heavy lifting for you.
I would suggest that after initializing and all that, you print a Javascript block from your .NET code, with something like this:
var markersFromDatabase = [
[60.164967,24.94758],
[59.956495, 10.764599]
//etc. This array should be printed from your serverside code
];
var markersToBeAdded = [];
jQuery.each(markersFromDatabase, function(indexOfItem, valueOfItem){
markersToBeAdded.push({
lat:valueOfItem[0],
lng:valueOfItem[1],
options: {
draggable: false,
icon: "img/your_awesome_icon.png",
title: "This is an icon from my database!"
}
});
});
jQuery("#map_canvas").gmap3(
{ action: 'addMarkers',
markers: markersToBeAdded
}
);
Edit: I realize now that I only answered half of your question. I'm afraid I have no apparent answer to the selection box. I suspect that you can use addRectangle or, in a worst case scenario, addFixPanel that lets you add a transparent <div> over your map canvas (and then trigger mouse events for that).
Here is one way to do it with Google Maps API 3's overlay editable feature.
Google Maps Overlay Editable
If your question is "How to select multiple markers with a rectangle", you can do something like this:
var markers = []; // This array must be filled with your data
var rectangles = [];
var triggeredMarkers = [];
google.maps.event.addListener(drawingManager, 'rectanglecomplete', updateSelection);
function updateSelection(rectangle){
var lat_max = rectangle.getBounds().getNorthEast().lat();
var lat_min = rectangle.getBounds().getSouthWest().lat();
var lng_max = rectangle.getBounds().getNorthEast().lng();
var lng_min = rectangle.getBounds().getSouthWest().lng();
rectangles.push(rectangle);
for(var i=0;i<markers.length;i++){
if((lat_min < markers[i].getPosition().lat() ? (markers[i].getPosition().lat() < lat_max ? true:false):false)
&& (lng_min < markers[i].getPosition().lng() ? (markers[i].getPosition().lng() < lng_max ? true:false):false)){
triggeredMarkers.push(markers[i]);
}
}
}
The rectangles array is used to hold rectangles in order to be able to erase them later.
But if you just want a simple function that handle Polygons, there is this one that really can be helpful for you. It can be used like this:
var markers = [];
var polygones = [];
var triggeredMarkers = [];
google.maps.event.addListener(drawingManager, 'polygoncomplete', updateSelection);
function updateSelection(polygon){
polygones.push(polygon);
for(var i=0;i<markers.length;i++){
if(google.maps.geometry.poly.containsLocation(markers[i].getPosition(),polygon)){
triggeredMarkers.push(markers[i]);
}
}
}
For rectangles and circles there's this answer that can help you.
I'm able to create an OpenLayers Map with maxExtent and restrictedExtent properties.
Also I have play with WMS layer and its maxExtent property, but the problem comes when use singleTile in the WMS layer.
What I want is to have a WMS layer that gets only one image from server but restricted to the bounds I want. Because this I used singleTile=true, ratio=1 and maxExtend=my_desired_extent.
But it always request the whole map view.
Any ideas how to achieve it.
Thanks in advance.
Next code shows a map with a base layer and a costline line layer I would like to use in singleTile mode and limiting the are they must request to WMS:
<script type="text/javascript" src="http://openlayers.org/api/2.11/OpenLayers.js"></script>
<div id="map" style="width: 700px; height: 500px;"></div>
<script type="text/javascript">
var map = new OpenLayers.Map("map");
var wms = new OpenLayers.Layer.WMS("OpenLayers WMS Basic", "http://labs.metacarta.com/wms/vmap0",
{
layers: 'basic'
});
map.addLayer(wms);
var restricted = new OpenLayers.Layer.WMS("Coast Line", "http://labs.metacarta.com/wms/vmap0",
{
layers: 'coastline_01,coastline_02'
},
{
opacity: 0.6,
singleTile: true,
ratio: 1,
isBaseLayer: false,
maxExtent: OpenLayers.Bounds.fromString("-10,-90,30,90")
});
map.addLayer(restricted);
map.setCenter(new OpenLayers.LonLat(0,40), 3);
</script>
I'm looking at OpenLayers source and seems what I want is not implemented so I need to create some WMS subclass or redefine some WMs layer methods.
I would appreciate any help.
Thanks.
If you are looking at restricting the whole map to the extent your talking about and not allowing users to pan outside of the extent you can try something like the following.
var extent = new OpenLayers.Bounds({-10,-90,30,90});
function init() {
var options = {
restrictedExtent: extent
};
map = new OpenLayers.Map('map', options);
var restricted = new OpenLayers.Layer.WMS("Coast Line", "http://labs.metacarta.com/wms/vmap0",
{
layers: 'coastline_01,coastline_02'
},
{
opacity: 0.6,
singleTile: true,
ratio: 1,
isBaseLayer: false
});
map.addLayer(restricted);
The difference here is that I have set the extent on the whole map not just the layer. Note that you will need to control the maximum zoom level as well. If you don't a user can still zoom out and data passed the extents will be shown though they will not be able to pan the view on the map.
If you want the map to pan outside of the extent specified but just not that layer then there are a number of approaches.
If you have access the the WMS server that the layer is served from you could add a setting there to restrict the extent of the layer server.
Check this link http://docs.geoserver.org/latest/en/user/webadmin/data/layers.html and read the "Bounding Boxes Section"
If you don't have access to the WMS server and you just want to restrict the area that is requested from the WMS server from the client side you could try using the OpenLayers.layer.WMS class with a different strategy. See below for an example:
var myfilter = new OpenLayers.Filter.Spatial({
type: OpenLayers.Filter.Spatial.BBOX,
value: OpenLayers.Bounds.({-10,-90,20,90})})
var filterStrategy = new OpenLayers.Strategy.Filter({filter: myFilter});
var restricted = new OpenLayers.Layer.WMS("Coast Line", "http://labs.metacarta.com/wms/vmap0",
{
layers: 'coastline_01,coastline_02'
},
{
opacity: 0.6,
singleTile: true,
ratio: 1,
isBaseLayer: false,
strategies: [filterStrategy]
});
map.addLayer(restricted);
Please note that I have not tested this code and it may take a little refining to get it to work.