Where is threshold for cluster strategy in openlayers 3? - openlayers-3

In openlayers 2.8 there was a threshold associated with the cluster strategy as per ticket https://trac.osgeo.org/openlayers/ticket/1815.
In openlayers 3 there is no mention of it anywhere (and the strategy paradigm seems to be gone as well).
http://openlayers.org/en/master/apidoc/ol.source.Cluster.html
Does anyone know if there exist a ticket for this feature?

The paradigm has changed considerably. In OpenLayers 3 you create a new layer, with a cluster style, and the "threshold" is set as a maxResolution, or minResolution in the layer's options.
Similar to:
var clusterLayer = new ol.layer.Vector({
visible: true,
zIndex: insightMap.totalServcies - element.SortOrder,
id: Id,
serviceId: element.Id,
minResolution: clusteringThreshold,
cluster: true,
});
You can also use minZoom and maxZoom according to the documentaiton, but I've encountered issues with them performing consistently.

Update
It's actually possible to have a proper cluster threshold without recompiling the library. You need to use the geometry of each feature (from the features property of the cluster) in the style function.
const noClusterStyles = [];
vectorLayer.setStyle(feature => {
const features = feature.get('features');
if (features.length > 5) {
return clusterStyle;
} else {
for (let i = 0; ii = features.length; i < ii; ++i) {
const clone = noClusterStyles[i] ? noClusterStyles[i] : noClusterStyle.clone();
clone.setGeometry(features[i].getGeometry());
noClusterStyles[i] = clone;
}
noClusterStyles.length = features.length;
return noClusterStyles;
}
});
Thank you to #ahocevar for the code snippet.
Another solution would require modifying the OL3 library itself.
The ol.source.Cluster.prototype.cluster_ function has the following code snippet :
var neighbors = this.source_.getFeaturesInExtent(extent);
ol.DEBUG && console.assert(neighbors.length >= 1, 'at least one neighbor found');
neighbors = neighbors.filter(function(neighbor) {
var uid = ol.getUid(neighbor).toString();
if (!(uid in clustered)) {
clustered[uid] = true;
return true;
} else {
return false;
}
});
// Add the following
// If one element has more too many neighbors, register it as a cluster of one
// Size-based styling should be handled separately, in the layer style function
let THRESHOLD= 3;
if(neighbors.length > THRESHOLD) {
this.features_.push(this.createCluster_(neighbors));
} else {
for(var j = 0 ; j < neighbors.length ; j++) {
this.features_.push(this.createCluster_([neighbors[j]]));
}
}

Related

Merging Multiple GLTF having some common nodes using gltf-transform

This issue is an Extension of Multiple GLTF loading and Merging on server side.
I am trying to merge multiple GLTF files that have some common nodes too.
The answer helped me for the merging the files and I combined the scenes by following code and it rendered perfectly
const scenes = root.listScenes()
const scene0 = scenes[0]
root.setDefaultScene(scene0);
if (scenes.length > 1) {
for (let i = 1; i < scenes.length; i++) {
let scene = scenes[i];
let nodes = scene.listChildren()
for (let j = 0; j < nodes.length; j++) {
scene0.addChild(nodes[j]);
}
}
}
root.listScenes().forEach((b, index) => index > 0 ? b.dispose() : null);
My issue is all the data that was common in the GLTFs is duplicated and this will create issue in animation when root bones are required to change. Is there a way to merge so that the common nodes are not duplicated ? I am also trying for some custom merge.
const gltfLoader = () => {
const document = new Document();
const root = document.getRoot();
document.merge(io.read(filePaths[0]));
let model;
for (let i = 1; i < filePaths.length; i++) {
const inDoc = new Document();
inDoc.merge(io.read(filePaths[i]));
model = inDoc.getRoot().listScenes()[0];
model.listChildren().forEach((child) => {
mergeStructure(root.listScenes()[0], child);
});
}
io.write('output.gltf', document);
}
const mergeStructure = (parent, childToMerge) => {
let contains = false;
parent.listChildren().forEach((child) => {
if (child.t === childToMerge.t && !contains && child.getName() === childToMerge.getName()) {
childToMerge.listChildren().forEach((subChild) => {
mergeStructure(child, subChild);
});
contains = true;
}
});
if (!contains) {
console.log("Adding " + childToMerge.getName() + " to " + parent.getName() + " as child")
parent.addChild(childToMerge);
}
}
But this merge is not working due to Error: Cannot link disconnected graphs/documents.
I am newbie to 3D modelling. Some direction would be great.
Thanks!
The error you're seeing above is occurring because the code attempts to move individual resources — e.g. Nodes — from one glTF Document to another. This isn't possible, each Document manages its resource graph internally, but an equivalent workflow would be:
Load N files and merge into one document (with N scenes).
import { Document, NodeIO } from '#gltf-transform/core';
const io = new NodeIO();
const document = new Document();
for (const path of paths) {
document.merge(io.read(path));
}
Iterate over all of the scenes, moving their children to some common scene:
const root = document.getRoot();
const mainScene = root.listScenes()[0];
for (const scene of root.listScenes()) {
if (scene === mainScene) continue;
for (const child of scene.listChildren()) {
// If conditions are met, append child to `mainScene`.
// Doing so will automatically detach it from the
// previous scene.
}
scene.dispose();
}
Clean up any remaining unmerged resources.
import { prune } from '#gltf-transform/functions';
await document.transform(prune());

How to extrude height ol3-Cesium?

I've got a GeoJson that I can load in ol3 without any problems.
var layer = new ol.layer.Vector({
source: new ol.source.Vector({
format: new ol.format.GeoJson(),
url: 'my_file.json',
})
});
My GeoJson have got a properties (let's say foo ) that I want to use to extrudedHeight.
I've done this in Cesium (see the Cesium exemple): http://cesiumjs.org/Cesium/Apps/Sandcastle/index.html?src=GeoJSON%20and%20TopoJSON.html&label=Showcases
But I can't find a way to do this on my ol3 layer.
Any clue ?
Edit:
I've hacked a bit and create my FeatureConverter like this:
test = {};
function extend(base, sub) {
// Avoid instantiating the base class just to setup inheritance
// See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/create
// for a polyfill
// Also, do a recursive merge of two prototypes, so we don't overwrite
// the existing prototype, but still maintain the inheritance chain
// Thanks to #ccnokes
var origProto = sub.prototype;
sub.prototype = Object.create(base.prototype);
for (var key in origProto) {
sub.prototype[key] = origProto[key];
}
// Remember the constructor property was set wrong, let's fix it
sub.prototype.constructor = sub;
// In ECMAScript5+ (all modern browsers), you can make the constructor property
// non-enumerable if you define it like this instead
Object.defineProperty(sub.prototype, 'constructor', {
enumerable: false,
value: sub
});
}
test.FeatureConverter = function(scene) {
olcs.FeatureConverter.call(this, scene);
}
test.FeatureConverter.prototype = {
olPolygonGeometryToCesium : function(layer, feature, olGeometry, projection, olStyle) {
olGeometry = olcs.core.olGeometryCloneTo4326(olGeometry, projection);
goog.asserts.assert(olGeometry.getType() == 'Polygon');
var rings = olGeometry.getLinearRings();
// always update Cesium externs before adding a property
var hierarchy = {};
var polygonHierarchy = hierarchy;
goog.asserts.assert(rings.length > 0);
for (var i = 0; i < rings.length; ++i) {
var olPos = rings[i].getCoordinates();
var positions = olcs.core.ol4326CoordinateArrayToCsCartesians(olPos);
goog.asserts.assert(positions && positions.length > 0);
if (i == 0) {
hierarchy.positions = positions;
} else {
hierarchy.holes = {
// always update Cesium externs before adding a property
positions: positions
};
hierarchy = hierarchy.holes;
}
}
var fillGeometry = new Cesium.PolygonGeometry({
// always update Cesium externs before adding a property
polygonHierarchy: polygonHierarchy,
perPositionHeight: true,
extrudedHeight: parseInt(feature.getProperties()['foo'])
});
var outlineGeometry = new Cesium.PolygonOutlineGeometry({
// always update Cesium externs before adding a property
polygonHierarchy: hierarchy,
perPositionHeight: true
});
var primitives = this.wrapFillAndOutlineGeometries(
layer, feature, olGeometry, fillGeometry, outlineGeometry, olStyle);
return this.addTextStyle(layer, feature, olGeometry, olStyle, primitives);
}
}
extend(olcs.FeatureConverter, test.FeatureConverter);
But it does not work... (and I do not know why...).

Updating a point in a line chart when data grouping is in effect

I need to update a specific point in the chart when new data arrives. I've written the following function:
function updatePoint(series, x, y) {
for (i = 0; i < series.data.length - 1; i++) {
var point = series.data[i];
if (point.x === x) {
point.update(y);
return;
}
}
}
This works fine, unless the chart has more than turboThreshold points, in which case the series.data is gone, and I only have series.xData and series.yData to work with. I tried the following variant, but the chart does not actually update:
function updateTurboPoint(series, x, y) {
for (i = 0; i < series.xData.length; i++) {
if (series.xData[i] === x) {
if (series.yData.length > i) {
series.yData[i] = y;
return;
}
}
}
}
Setting a breakpoint on the return, I verified that series.yData[i] has the new value, even though it did not appear on the chart. How can I get this to actually update the chart?
I am using HighStock 2.0.4.
EDIT: Created a JSFiddle: http://jsfiddle.net/swish014/2unh1cLa/
EDIT: Changed the title, as I (now) do not believe turbo mode has anything to do with it.
The two comments were key to figuring out my own problem.
It is related to dataGrouping (and not turbo mode as I first thought).
The series.points array does exist, and updating a point there does update the chart as desired.
In my question, I listed two separate functions to update points, but I believe I can use just one function:
function updatePoint(series, x, y) {
for (i = 0; i < series.points.length - 1; i++) {
var point = series.points[i];
if (point.x === x) {
point.update(y);
return;
}
}
}

add each dataLabel a function on mouseover/mouseout

I want to add each dataLabel a (just call it "highlightPoint()") function on a mouseover event. It works, if I add this function to a single point.
chartObj.series[0].data[1].dataLabel.on("mouseover", function () {
chartObj.series[0].data[1].setState('hover');
});
It doesn't work if I loop threw my data points. I guess the reference to each individual point is incorrect or something like that.
jsfiddle-link
After you have looped through both data sets, i = 2 and k = 1. So the mouse events are always trying to use i=2 and k=1 to access the data point, which of course is out of bounds. See this fiddle: http://jsfiddle.net/bLvfS/1/
for (var i = 0; i < chartObj.series.length; i++) {
for (var k = 0; k < chartObj.series[i].data.length; k++) {
var onmouseover = function(u, j) {
return function() {chartObj.series[u].data[j].setState('hover');};
}
var onmouseout = function(u, j) {
return function() {chartObj.series[u].data[j].setState();};
}
chartObj.series[i].data[k].dataLabel.on("mouseover", onmouseover(i,k));
chartObj.series[i].data[k].dataLabel.on("mouseout", onmouseout(i,k));
}
}
I've added functions that get passed the current i,k pair and return the actual function you want to run on the mouse events. Maybe someone has a better solution... but it seems to work.
Problem is with closures, see working fiddle: http://jsfiddle.net/Fusher/bLvfS/2/
for (var i = 0; i < chartObj.series.length; i++) {
for (var k = 0; k < chartObj.series[i].data.length; k++) {
(function(i,k){
chartObj.series[i].data[k].dataLabel.on("mouseover", function () {
chartObj.series[i].data[k].setState('hover');
});
chartObj.series[i].data[k].dataLabel.on("mouseout", function () {
chartObj.series[i].data[k].setState();
});
})(i,k);
}
}

Looking for speedups for A* search

I've got the following working A* code in C#:
static bool AStar(
IGraphNode start,
Func<IGraphNode, bool> check,
out List<IGraphNode> path)
{
// Closed list. Hashset because O(1).
var closed =
new HashSet<IGraphNode>();
// Binary heap which accepts multiple equivalent items.
var frontier =
new MultiHeap<IGraphNode>(
(a, b) =>
{ return Math.Sign(a.TotalDistance - b.TotalDistance); }
);
// Some way to know how many multiple equivalent items there are.
var references =
new Dictionary<IGraphNode, int>();
// Some way to know which parent a graph node has.
var parents =
new Dictionary<IGraphNode, IGraphNode>();
// One new graph node in the frontier,
frontier.Insert(start);
// Count the reference.
references[start] = 1;
IGraphNode current = start;
do
{
do
{
frontier.Get(out current);
// If it's in the closed list or
// there's other instances of it in the frontier,
// and there's still nodes left in the frontier,
// then that's not the best node.
} while (
(closed.Contains(current) ||
(--references[current]) > 0) &&
frontier.Count > 0
);
// If we have run out of options,
if (closed.Contains(current) && frontier.Count == 0)
{
// then there's no path.
path = null;
return false;
}
closed.Add(current);
foreach (var edge in current.Edges)
{
// If there's a chance of a better path
// to this node,
if (!closed.Contains(edge.End))
{
int count;
// If the frontier doesn't contain this node,
if (!references.TryGetValue(edge.End, out count) ||
count == 0)
{
// Initialize it and insert it.
edge.End.PathDistance =
current.PathDistance + edge.Distance;
edge.End.EstimatedDistance = CalcDistance(edge.End);
parents[edge.End] = current;
frontier.Insert(edge.End);
references[edge.End] = 1;
}
else
{
// If this path is better than the existing path,
if (current.PathDistance + edge.Distance <
edge.End.PathDistance)
{
// Use this path.
edge.End.PathDistance = current.PathDistance +
edge.Distance;
parents[edge.End] = current;
frontier.Insert(edge.End);
// Keeping track of multiples equivalent items.
++references[edge.End];
}
}
}
}
} while (!check(current) && frontier.Count > 0);
if (check(current))
{
path = new List<IGraphNode>();
path.Add(current);
while (current.PathDistance != 0)
{
current = parents[current];
path.Add(current);
}
path.Reverse();
return true;
}
// Yep, no path.
path = null;
return false;
}
How do I make it faster? No code samples, please; that's a challenge I've set myself.
Edit: To clarify, I'm looking for any advice, suggestions, links, etc. that apply to A* in general. The code is just an example. I asked for no code samples because they make it too easy to implement the technique(s) being described.
Thanks.
Have you looked at this page or this page yet? They have plenty of helpful optimization tips as well as some great information on A* in general.
Change to using a Random Meldable Queue for the heap structure. Since you wanted a programming challenge, I won't show you how I changed the recursive Meld method to not be recursive. That's the trick to getting speed out of that structure. More info in Gambin's paper "Randomized Meldable Priority Queues" (search on the web for that).

Resources