I have an OpenLayer Marker Animation implemented in my (python) flask application.
However in my dataset that I am using for the Marker Animation I have an extra route that I would like to show an animation of as well.
When I add code to take care of the second animation it is like the controls for the first animation is overwritten and I have no idea of how to avoid this from happening. I've tried renaming all the vars to 1 and 2 to avoiding them from overwriting each other, but for some reason it seems like it still overwrites the controls.
I have created this fiddle to illustrate the problem. If you click the start animation under Map 1 it will run the animation on Map 2. I have just used the procedure below in my fiddle, but originally I had the code in a for-loop that was run twice to extract both routes and then draw the maps.
Route points for Map 1
Set up Map 1
Bind functions to Map 1 buttons
Route points for Map 2
Set up Map 2
Bind functions to Map 2 buttons
I guess my problem is about isolating code and/or bindings. First I tried renaming the functions (moveFeature, startAnimation, stopAnimation) to their respective names with either a "1" or "2" added. That did not do the trick, nor did renaming all the variables similarly.
You are creating a lot of variables with the same name. For example, when you overwrite the function StartAnimation, the buttons enter in the second one (Since both are pointing to a function called "StartAnimation").
To avoid this, you have to create different variables for each map. This way you would avoid unuseful code and can be sure that each element is using the variables they are supposed to use.
I've built an example based on your jsfiddle code:
var locations1 = [[53.44241609, 6.84913974], [53.44241894, 6.84913726], [53.44242156, 6.84913385], [53.44242473, 6.84913076], [53.44242859, 6.84912721], [53.44243324, 6.84912446], [53.44243724, 6.84912303], [53.44243994, 6.84912206], [53.44244199, 6.84911994], [53.44244474, 6.84911928], [53.44244757, 6.8491193], [53.44245181, 6.84911968], [53.44245596, 6.84912085], [53.44246139, 6.84912072], [53.4424669, 6.84912142], [53.44247222, 6.84912279], [53.4424778, 6.84912454], [53.44248644, 6.84912644], [53.44249062, 6.84912761], [53.44249409, 6.84913057], [53.44249746, 6.84913362], [53.44250197, 6.84913592], [53.44250901, 6.84913629], [53.44251198, 6.84913792], [53.44251293, 6.84913988], [53.44251458, 6.84914126], [53.44251596, 6.8491434], [53.44251778, 6.84914727], [53.44251988, 6.8491501], [53.44252248, 6.8491531], [53.44252517, 6.84915473], [53.44252316, 6.84915181], [53.44252377, 6.84915124], [53.4425233, 6.84914949], [53.44252341, 6.84914848], [53.44252276, 6.84914827], [53.44252397, 6.84914868], [53.4425216, 6.84914477], [53.44252001, 6.84914287], [53.44252107, 6.84914273], [53.44251986, 6.84913869], [53.44251841, 6.84913463], [53.44251482, 6.84912822], [53.44251525, 6.84912649], [53.4425148, 6.84912465], [53.44251483, 6.84912049], [53.44251625, 6.84911749], [53.44251677, 6.84911403], [53.4425187, 6.84910978], [53.44252028, 6.84910694], [53.44252218, 6.84910622], [53.44252457, 6.84910649], [53.44252783, 6.84910729], [53.44253168, 6.84910888], [53.44253668, 6.84910943], [53.44254088, 6.84910976], [53.44254363, 6.84910898], [53.44254612, 6.84910996], [53.44254803, 6.84910946], [53.44255004, 6.84910945], [53.44255416, 6.84910766], [53.44256019, 6.84910343], [53.44256469, 6.84909908], [53.44256753, 6.84909764], [53.44257106, 6.84909639], [53.44257482, 6.84909654], [53.44257861, 6.84909769]];
var locations2 = [[53.44241609, 6.84913974], [53.44241894, 6.84913726], [53.44242156, 6.84913385], [53.44242473, 6.84913076], [53.44242859, 6.84912721], [53.44243324, 6.84912446], [53.44243724, 6.84912303], [53.44243994, 6.84912206], [53.44244199, 6.84911994], [53.44244474, 6.84911928], [53.44244757, 6.8491193], [53.44245181, 6.84911968], [53.44245596, 6.84912085], [53.44246139, 6.84912072], [53.4424669, 6.84912142], [53.44247222, 6.84912279], [53.4424778, 6.84912454], [53.44248644, 6.84912644], [53.44249062, 6.84912761], [53.44249409, 6.84913057], [53.44249746, 6.84913362], [53.44250197, 6.84913592], [53.44250901, 6.84913629], [53.44251198, 6.84913792], [53.44251293, 6.84913988], [53.44251458, 6.84914126], [53.44251596, 6.8491434], [53.44251778, 6.84914727], [53.44251988, 6.8491501], [53.44252248, 6.8491531], [53.44252517, 6.84915473], [53.44252316, 6.84915181], [53.44252377, 6.84915124], [53.4425233, 6.84914949], [53.44252341, 6.84914848], [53.44252276, 6.84914827], [53.44252397, 6.84914868], [53.4425216, 6.84914477], [53.44252001, 6.84914287], [53.44252107, 6.84914273], [53.44251986, 6.84913869], [53.44251841, 6.84913463], [53.44251482, 6.84912822], [53.44251525, 6.84912649], [53.4425148, 6.84912465], [53.44251483, 6.84912049], [53.44251625, 6.84911749], [53.44251677, 6.84911403], [53.4425187, 6.84910978], [53.44252028, 6.84910694], [53.44252218, 6.84910622], [53.44252457, 6.84910649], [53.44252783, 6.84910729], [53.44253168, 6.84910888], [53.44253668, 6.84910943], [53.44254088, 6.84910976], [53.44254363, 6.84910898], [53.44254612, 6.84910996], [53.44254803, 6.84910946], [53.44255004, 6.84910945], [53.44255416, 6.84910766], [53.44256019, 6.84910343], [53.44256469, 6.84909908], [53.44256753, 6.84909764], [53.44257106, 6.84909639], [53.44257482, 6.84909654], [53.44257861, 6.84909769]];
locations1.map(function(l) {
return l.reverse();
});
locations2.map(function(l) {
return l.reverse();
});
// ---------------------------
//Defining Map 1 and Events
// ---------------------------
var route1 = new ol.geom.LineString(locations1)
.transform('EPSG:4326', 'EPSG:3857');
var routeCoords1 = route1.getCoordinates();
var routeLength1 = routeCoords1.length;
var routeFeature1 = new ol.Feature({
type: 'route',
geometry: route1
});
var geoMarker1 = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords1[0])
});
var startMarker1 = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords1[0])
});
var endMarker1 = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords1[routeLength1 - 1])
});
var styles1 = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v3.20.1/examples/data/icon.png'
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'black'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating1 = false;
var speed1, now1;
var speedInput1 = document.getElementById('speed1');
var startButton1 = document.getElementById('start-animation1');
var vectorLayer1 = new ol.layer.Vector({
source: new ol.source.Vector({
features: [routeFeature1, geoMarker1, startMarker1, endMarker1]
}),
style: function(feature) {
// hide geoMarker if animation is active
if (animating1 && feature.get('type') === 'geoMarker') {
return null;
}
return styles1[feature.get('type')];
}
});
var map1 = new ol.Map({
target: document.getElementById('map1'),
loadTilesWhileAnimating: true,
view: new ol.View(),
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vectorLayer1
]
});
map1.getView().fit(
vectorLayer1.getSource().getExtent(), map1.getSize(),
{padding: [30, 5, 5, 5]});
var center1 = map1.getView().getCenter();
var moveFeature1 = function(event) {
var vectorContext = event.vectorContext;
var frameState = event.frameState;
if (animating1) {
var elapsedTime = frameState.time - now1;
// here the trick to increase speed is to jump some indexes
// on lineString coordinates
var index = Math.round(speed1 * elapsedTime / 1000);
if (index >= routeLength1) {
stopAnimation1(true);
return;
}
var currentPoint = new ol.geom.Point(routeCoords1[index]);
var feature = new ol.Feature(currentPoint);
vectorContext.drawFeature(feature, styles1.geoMarker);
}
// tell OL3 to continue the postcompose animation
map1.render();
};
function startAnimation1() {
if (animating1) {
stopAnimation1(false);
} else {
animating1 = true;
now1 = new Date().getTime();
speed1 = speedInput1.value;
startButton1.textContent = 'Cancel Animation';
// hide geoMarker
geoMarker1.setStyle(null);
// just in case you pan somewhere else
map1.getView().setCenter(center1);
map1.on('postcompose', moveFeature1);
map1.render();
}
}
function stopAnimation1(ended) {
animating1 = false;
startButton1.textContent = 'Start Animation';
// if animation cancelled set the marker at the beginning
var coord = ended ? routeCoords1[routeLength1 - 1] : routeCoords1[0];
/** #type {ol.geom.Point} */
(geoMarker1.getGeometry())
.setCoordinates(coord);
//remove listener
map1.un('postcompose', moveFeature1);
}
startButton1.addEventListener('click', startAnimation1, false);
// ---------------------------
//Defining Map 2 and Events
// ---------------------------
var route2 = new ol.geom.LineString(locations2)
.transform('EPSG:4326', 'EPSG:3857');
var routeCoords2 = route2.getCoordinates();
var routeLength2 = routeCoords2.length;
var routeFeature2 = new ol.Feature({
type: 'route',
geometry: route2
});
var geoMarker2 = new ol.Feature({
type: 'geoMarker',
geometry: new ol.geom.Point(routeCoords2[0])
});
var startMarker2 = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords2[0])
});
var endMarker2 = new ol.Feature({
type: 'icon',
geometry: new ol.geom.Point(routeCoords2[routeLength2 - 1])
});
var styles2 = {
'route': new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: [237, 212, 0, 0.8]
})
}),
'icon': new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'https://openlayers.org/en/v3.20.1/examples/data/icon.png'
})
}),
'geoMarker': new ol.style.Style({
image: new ol.style.Circle({
radius: 7,
snapToPixel: false,
fill: new ol.style.Fill({
color: 'black'
}),
stroke: new ol.style.Stroke({
color: 'white',
width: 2
})
})
})
};
var animating2 = false;
var speed2, now2;
var speedInput2 = document.getElementById('speed2');
var startButton2 = document.getElementById('start-animation2');
var vectorLayer2 = new ol.layer.Vector({
source: new ol.source.Vector({
features: [routeFeature2, geoMarker2, startMarker2, endMarker2]
}),
style: function(feature) {
// hide geoMarker if animation is active
if (animating2 && feature.get('type') === 'geoMarker') {
return null;
}
return styles2[feature.get('type')];
}
});
var map2 = new ol.Map({
target: document.getElementById('map2'),
loadTilesWhileAnimating: true,
view: new ol.View(),
layers: [
new ol.layer.Tile({
source: new ol.source.OSM()
}),
vectorLayer2
]
});
map2.getView().fit(
vectorLayer2.getSource().getExtent(), map2.getSize(),
{padding: [30, 5, 5, 5]});
var center2 = map2.getView().getCenter();
var moveFeature2 = function(event) {
var vectorContext = event.vectorContext;
var frameState = event.frameState;
if (animating2) {
var elapsedTime = frameState.time - now2;
// here the trick to increase speed is to jump some indexes
// on lineString coordinates
var index = Math.round(speed2 * elapsedTime / 1000);
if (index >= routeLength2) {
stopAnimation2(true);
return;
}
var currentPoint = new ol.geom.Point(routeCoords2[index]);
var feature = new ol.Feature(currentPoint);
vectorContext.drawFeature(feature, styles2.geoMarker);
}
// tell OL3 to continue the postcompose animation
map2.render();
};
function startAnimation2() {
if (animating2) {
stopAnimation2(false);
} else {
animating2 = true;
now2 = new Date().getTime();
speed2 = speedInput2.value;
startButton2.textContent = 'Cancel Animation';
// hide geoMarker
geoMarker2.setStyle(null);
// just in case you pan somewhere else
map2.getView().setCenter(center2);
map2.on('postcompose', moveFeature2);
map2.render();
}
}
function stopAnimation2(ended) {
animating2 = false;
startButton2.textContent = 'Start Animation';
// if animation cancelled set the marker at the beginning
var coord = ended ? routeCoords2[routeLength2 - 1] : routeCoords2[0];
/** #type {ol.geom.Point} */
(geoMarker2.getGeometry())
.setCoordinates(coord);
//remove listener
map2.un('postcompose', moveFeature2);
}
startButton2.addEventListener('click', startAnimation2, false);
<script src="https://openlayers.org/en/v3.20.1/build/ol.js"></script>
<link href="https://openlayers.org/en/v3.20.1/css/ol.css" rel="stylesheet"/>
<h1>
Map 1
</h1>
<div id="map1" class="map"></div>
<label for="speed1">
speed:
<input id="speed1" type="range" min="10" max="999" step="10" value="60">
</label>
<button id="start-animation1">Start Animation</button>
<h1>
Map 2
</h1>
<div id="map2" class="map"></div>
<label for="speed2">
speed:
<input id="speed2" type="range" min="10" max="999" step="10" value="60">
</label>
<button id="start-animation2">Start Animation</button>
For my company, I must develop a function who draw a path with GPS coordinates. Indeed, my company use GPS to track runners during multiple race.
So, i tried many differents way to draw my polyline, my last version is:
_public.drawPolyline = function(pool, id, points, color, opacity, weight) {
try {
var l = points.length;
var latlngs = [];
var j=1;
for (var i = 0; i < l; i++) {
latlngs[i] = new ol.geom.Point(ol.proj.transform([points[i].longitude, points[i].latitude], 'EPSG:4326', 'EPSG:3857'));
};
var style = new ol.style.Style({
stroke: new ol.style.Stroke({
color: color,
width: weight,
opacity: opacity,
radius: 6
})
});
//Check if pool exists, else create it
if (!_private._polyline.containsKey(pool)) {
_private._polyline.put(pool, new jQuery.Hashtable())
}
var currentPool = _private._polyline.get(pool);
//Check if line exists, if yes, update path
if (currentPool.containsKey(id)) {
var vectorLayer = currentPool.get(id).layer;
vectorLayer.setVisible(true);
} else {
var linefeature = new ol.source.Vector('Path', {styleMap: style});
var comp = new ol.geom.LineString(latlngs);
var featurecomp = new ol.Feature({
name: "Comp",
geometry: comp
});
var vector = new ol.layer.Vector({
title: pool,
visible: true,
source: linefeature
});
linefeature.addFeatures(featurecomp);
currentPool.put(id, linefeature);
currentPool.put(id, { "type": "Path", "url": id, "layer": vector });
var vectorLayer = currentPool.get(id).layer;
vectorLayer.setVisible(true);
}
} catch (e) {
console.log(e.message);
}
}
So, I wanted to draw a Polyline with a function with differents parameters:
- pool: an Hashtable storing my polyline
- id: not important
- points: Contain an array of objects (
{"location":[{"id":1854703,"latitude":42.831,"longitude":0.30087,"altitude":0,"hpl":0,"vpl":0,"speed":4,"direction":258,"date":"2012-08-25 03:43:23","device_id":786,"datereceived":"2012-08-25 03:43:23"}).
According to my test server, I have no error in my logs, but, I still don't have a polyline drawed.
If someone could help me with this, it would be great.
Regards, Brz.
You just need to create LineString points as below
points.push(ol.proj.transform([xx,yy],'EPSG:4326', 'EPSG:3857'));
Demo Link https://plnkr.co/edit/WqWoFzjQdPDRkAjeXOGn?p=preview
Edits
var vectorSource = new ol.source.Vector({});
var vectorSourcePoint = new ol.source.Vector({});
var style = new ol.style.Style({
image: new ol.style.Circle({
radius: 4,
fill: new ol.style.Fill({
color: color
}),
stroke: new ol.style.Stroke({
color: color,
width: weight,
opacity: opacity
})
})
});
var l = points.length;
var latlngs = [];
for (var i = 0; i < l; i++) {
latlngs.push(ol.proj.transform([points[i].longitude, points[i].latitude],'EPSG:4326', 'EPSG:3857'));
//below 3 lines of code creates point geometry. I think you don't need this
var point = new ol.geom.Point([points[i].longitude, points[i].latitude]).transform('EPSG:4326', 'EPSG:3857');
var fea = new ol.Feature({geometry:point});
vectorSourcePoint.addFeature(fea);
};
//below lines of code creates polyline. You are missing these lines.
var thing = new ol.geom.MultiLineString([points]);
var featurething = new ol.Feature({
name: "Thing",
geometry: thing
});
vectorSource.addFeature( featurething );
/*
var young_link = {
power: 30,
cpower: 20,
hp: 3,
image: "../images/young_link.jpg",
};
var young_zelda = {
power: 30,
cpower: 20,
hp: 3,
}
var impa = {
power: 30,
cpower: 20,
hp: 3,
}
var hey = {
power: 30,
cpower: 20,
hp: 3,
}
//$("#test").html(young_link);
console.log(young_link);*/
$(document).ready(function() {
var hero_image = new Array();
hero_image[0] = new Image();
hero_image[0].src = 'assets/images/link.png';
hero_image[0].id = 'image';
hero_image[1] = new Image();
hero_image[1].src = 'assets/images/bongo.png';
hero_image[1].id = 'image';
hero_image[2] = new Image();
hero_image[2].src = 'assets/images/gandondorf.jpg';
hero_image[2].id = 'image';
hero_image[3] = new Image();
hero_image[3].src = 'assets/images/queen.png';
hero_image[3].id = 'image';
//var test = "<img src= '../images/young_link.jpg'>";
//var young_hero = ["young_link","young_zelda","impa", "malon"];
var young_hero = ["Link", "Bongo Bongo","Gandondorf","Queen Gohma"];
var health = [100, 70, 120, 50];
for (var i = 0; i < young_hero.length; i++) {
var hero_btns = $("<buttons>");
hero_btns.addClass("hero hero_button");
hero_btns.attr({"data-name":young_hero[i],"data-health":health[i],"data-image":hero_image[i]});
hero_btns.text(young_hero[i]);
hero_btns.append(hero_image[i]);
hero_btns.append(health[i]);
$("#buttons").append(hero_btns);
}
$(".hero_button").on("click" , function() {
var battle_ground = $("<div>");
battle_ground.addClass("hero hero_button");
battle_ground.text($(this).data("data-name"));
$("#battle").append(battle_ground);
});
});
The for loop is working and appending the buttons on the screen. But in $(".hero_button").on("click" , function() it is just putting a empty box on the page with a click. So, it is not taking the data that is attached to the button.
Sam answered your question correctly and rightly deserves the accepted answer. But I wanted to give you an insight into how you can do this in a cleaner way, without lots of arrays which must line up. Also without using jQuery at all. Below you can see a more object oriented way to do this.
You can see it in action in this jsFiddle
// Now we have an object which represents a hero. No need to duplicate loads of code.
function Hero(heroData) {
this.name = heroData.name;
this.health = heroData.health;
this.setImage = function() {
this.image = new Image();
this.image.src = heroData.imageSrc;
this.image.id = heroData.imageId;
}
this.createHeroButton = function() {
this.createButtonElement();
this.addButtonToPage();
this.attachButtonEvents();
}
this.createButtonElement = function() {
var heroButton = document.createElement('button');
heroButton.classList.add('hero,hero_button');
heroButton.setAttribute('name', this.name);
heroButton.setAttribute('health', this.health);
heroButton.appendChild(this.image);
this.button = heroButton;
}
this.attachButtonEvents = function() {
this.button.addEventListener('click', this.addButtonToPage.bind(this));
}
this.addButtonToPage = function() {
var container = document.getElementById('container');
container.appendChild(this.button);
}
this.takeDamage = function(damageValue) {
this.health -= damageValue;
this.button.setAttribute('health', this.health);
}
this.setImage();
}
// So here we create a Hero instance, in this case Link, we can use now describe links attributes, image, name, health...
var link = new Hero({
name: 'Link',
health: 100,
imageSrc: 'http://orig12.deviantart.net/8bb7/f/2011/276/4/e/four_swords_link_avatar_by_the_missinglink-d4bq8qn.png',
imageId: 'link-image'
});
var mario = new Hero({
name: 'Mario',
health: 100,
imageSrc: 'http://rs568.pbsrc.com/albums/ss123/stvan000/thumb-super-mario-bros-8bit-Mario.jpg~c200',
imageId: 'mario-image'
});
// Now we can easily make a button and add it to the page
link.createHeroButton();
mario.createHeroButton();
// Lets try decreasing the health on mario
mario.takeDamage(10);
// Because we have an object reference which handles all of our heros state we can decrease his health and update the buttons data without much trouble.
A couple of changes to get the data set and read correctly:
make button tags instead of buttons
use .attr() instead of .data() to get the attributes
See comments inline in the code below.
Also, instead of adding an attribute for the Image object of each item (which will add an attribute like data-image="[Object object]") just add an integer corresponding to the iterator index and use that to reference into the hero_image array when you need to get the corresponding image.
Additionally, you can use Array.forEach() to iterate over the items in the heroes array with a callback function. That way you don't have to worry about updating the iterator variable (i in this case) and indexing into the array. You should take a look at this functional programming guide which has some good exercises.
$(document).ready(function() {
var hero_image = new Array();
hero_image[0] = new Image();
hero_image[0].src = 'assets/images/link.png';
hero_image[0].id = 'image';
hero_image[1] = new Image();
hero_image[1].src = 'assets/images/bongo.png';
hero_image[1].id = 'image';
hero_image[2] = new Image();
hero_image[2].src = 'assets/images/gandondorf.jpg';
hero_image[2].id = 'image';
hero_image[3] = new Image();
hero_image[3].src = 'assets/images/queen.png';
hero_image[3].id = 'image';
var young_heroes = ["Link", "Bongo Bongo", "Gandondorf", "Queen Gohma"];
var health = [100, 70, 120, 50];
young_heroes.forEach(function(young_hero,i) {
var hero_btns = $("<button>");
hero_btns.addClass("hero hero_button");
hero_btns.attr({
"data-name": young_hero,
"data-health": health[i],
//instead of adding an attribute for the image object, just add an index
"data-index": i
});
hero_btns.text(young_hero);
hero_btns.append(hero_image[i]);
hero_btns.append(health[i]);
$("#buttons").append(hero_btns);
});
$(".hero_button").on("click", function() {
var battle_ground = $("<div>");
battle_ground.addClass("hero hero_button");
//use .attr() here instead of .data()
battle_ground.text($(this).attr("data-name"));
/** My additions -
* I am not sure exactly how this should be done
* so adjust accordingly
**/
//additionally, you can add attributes to the new battle_ground item
battle_ground.attr('data-health',$(this).attr("data-health"));
battle_ground.append(hero_image[$(this).attr("data-index")]);
battle_ground.append($(this).attr("data-health"));
/** End my additions **/
$("#battle").append(battle_ground);
});
});
#battle div {
border: 1px solid #555;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="buttons"></div>
Battle ground:
<div id="battle"></div>
the following question was already raised on "OL3 Dev" but it has been moved to StackOverflow to comply with the group policies.
I've been using OpenLayers 3 for some time and implemented a simple test application where I simulate the movement of several objects on a map.
I use a Vector layer and a corresponding Vector source.
Let's say I have about 1000 features with a ol.geom.Point geometries that are updated every 20-30secs.
I can obtain pretty good results by modifying the geometry coordinates, the result is smooth and works ok.
Now I tried to use the Cluster functionality, to group closed features. Unfortunately in this case the result is very slow and irregular.
I think the problem is due to the change() event being fired every time the geometry of a single feature is changed, so i was wondering:
is there a way to prevent the modification of a feature to be immediately taken into consideration by the Cluster, and fire it only at a specific interval?
Here below you can find two examples, the first without the Cluster source and the second with it.
No Cluster: http://jsfiddle.net/sparezenny/dwLpmqvc/
var mySource = new ol.source.Vector({
features : new Array()
});
var myLayer = new ol.layer.Vector({
source: mySource,
style: function(feature, resolution) {
var myStyle = [new ol.style.Style({
image: new ol.style.Circle({
radius: 10,
stroke: new ol.style.Stroke({
color: '#fff'
}),
fill: new ol.style.Fill({
color: '#3399CC'
})
})
})];
return myStyle;
}
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.MapQuest({layer: 'sat'})
}),
myLayer
],
view: new ol.View({
center: ol.proj.transform([37.41, 8.82], 'EPSG:4326', 'EPSG:3857'),
zoom: 4
})
});
var positions = new Array();
function Mock(id, longitude, latitude){
this.id=id;
this.latitude=latitude;
this.longitude=longitude;
};
function updatePositions(mocks){
var featuresToBeAdded = new Array();
for (var i=0; i < mocks.length; i++){
var mock = mocks[i];
var id = mock.id;
var previousPosition = positions[id];
var resultFeature;
var position;
// new
if (previousPosition==undefined || previousPosition==null){
position = ol.proj.transform([ mock.longitude, mock.latitude ],
'EPSG:4326', 'EPSG:3857');
positions[id] = mock;
resultFeature = new ol.Feature({
geometry: new ol.geom.Point(position)
});
featuresToBeAdded.push(resultFeature);
}
// update
else{
resultFeature = positions[id].feature;
positions[id] = mock;
position = ol.proj.transform([ mock.longitude, mock.latitude ],
'EPSG:4326', 'EPSG:3857');
resultFeature.getGeometry().setCoordinates(position);
}
positions[id].feature = resultFeature;
}
if (featuresToBeAdded.length>0){
mySource.addFeatures(featuresToBeAdded);
}
//map.render();
}
var myMocks = new Array(1000);
for (var i=0; i<1000; i++){
myMocks[i] = new Mock(i,
37.41+(Math.random()>0.5?0.01:-0.01)*i,
8.82 +(Math.random()>0.5?0.01:-0.01)*i);
}
setInterval(
function(){
var j = Math.round(Math.random()*980);
for (var i=0; i<20; i++){
myMocks[j+i].latitude = myMocks[j+i].latitude + (Math.random()>0.5?0.01:-0.01);
myMocks[j+i].longitude = myMocks[j+i].longitude + (Math.random()>0.5?0.01:-0.01);
}
console.debug("updatePositions..");
updatePositions(myMocks);
}, 5000);
Cluster: http://jsfiddle.net/sparezenny/gh7ox9nj/
var mySource = new ol.source.Vector({
features : new Array()
});
var clusterSource = new ol.source.Cluster({
distance: 10,
source: mySource
});
var myLayer = new ol.layer.Vector({
source: clusterSource,
style: function(feature, resolution) {
var clusteredFeatures = feature.get('features');
var size = feature.get('features').length;
var myStyle = [new ol.style.Style({
image: new ol.style.Circle({
radius: 10,
stroke: new ol.style.Stroke({
color: '#fff'
}),
fill: new ol.style.Fill({
color: '#3399CC'
})
}),
text: new ol.style.Text({
text: size.toString(),
fill: new ol.style.Fill({
color: '#fff'
})
})
})];
return myStyle;
}
});
var map = new ol.Map({
target: 'map',
layers: [
new ol.layer.Tile({
source: new ol.source.MapQuest({layer: 'sat'})
}),
myLayer
],
view: new ol.View({
center: ol.proj.transform([37.41, 8.82], 'EPSG:4326', 'EPSG:3857'),
zoom: 4
})
});
var positions = new Array();
function Mock(id, longitude, latitude){
this.id=id;
this.latitude=latitude;
this.longitude=longitude;
};
function updatePositions(mocks){
var featuresToBeAdded = new Array();
for (var i=0; i < mocks.length; i++){
var mock = mocks[i];
var id = mock.id;
var previousPosition = positions[id];
var resultFeature;
var position;
// new
if (previousPosition==undefined || previousPosition==null){
position = ol.proj.transform([ mock.longitude, mock.latitude ],
'EPSG:4326', 'EPSG:3857');
positions[id] = mock;
resultFeature = new ol.Feature({
geometry: new ol.geom.Point(position)
});
featuresToBeAdded.push(resultFeature);
}
// update
else{
resultFeature = positions[id].feature;
positions[id] = mock;
position = ol.proj.transform([ mock.longitude, mock.latitude ],
'EPSG:4326', 'EPSG:3857');
resultFeature.getGeometry().setCoordinates(position);
}
positions[id].feature = resultFeature;
}
if (featuresToBeAdded.length>0){
mySource.addFeatures(featuresToBeAdded);
}
//map.render();
}
var myMocks = new Array(1000);
for (var i=0; i<1000; i++){
myMocks[i] = new Mock(i,
37.41+(Math.random()>0.5?0.01:-0.01)*i,
8.82 +(Math.random()>0.5?0.01:-0.01)*i);
}
setInterval(
function(){
var j = Math.round(Math.random()*980);
for (var i=0; i<20; i++){
myMocks[j+i].latitude = myMocks[j+i].latitude + (Math.random()>0.5?Math.random()*0.01:-Math.random()*0.01);
myMocks[j+i].longitude = myMocks[j+i].longitude + (Math.random()>0.5?Math.random()*0.01:-Math.random()*0.01);
}
console.debug("updatePositions..");
updatePositions(myMocks);
}, 5000);
As you can see I have 1000 features and I try to update the positions of 20 of them every 5 seconds.
In the first case the interaction with the map is smooth, whilst in the second case it often stops or slow down.
Any clue or advise about how to avoid that?
Thanks in advance
This is an old link now, but as I've encountered exactly the same issue, I thought I'd post my work-around.
The idea is to cripple the offending event handler in the cluster source, and only fire that same code every frame render.
Note that the code as presented:
Is really quite a hack, as it's messing about with private functions.
Because of the above, it may well need modifying for different non-debug builds.
Uses a newer version of OL3 than the OP (see also point 2!).
JSFiddle here
if (ol.source.Cluster.prototype.onSourceChange_)
{
// Make a new pointer to the old sourceChange function.
ol.source.Cluster.prototype.newSourceChange =
ol.source.Cluster.prototype.onSourceChange_;
// Nuke the old function reference.
ol.source.Cluster.prototype.onSourceChange_ = function() {};
}
if (ol.source.Cluster.prototype.Ra)
{
// As above, but for non-debug code.
ol.source.Cluster.prototype.newSourceChange =
ol.source.Cluster.prototype.Ra;
ol.source.Cluster.prototype.Ra = function() {};
}
// Later on..
map.on('postrender', clusterSource.newSourceChange, clusterSource);
As you can see, this comfortably handles the 1000 features even with a 100ms update time.
I'm trying to implement two surfaces, connected with a spring, that would react to drag in famo.us. So far I have setup the surfaces, can drag those, have a spring that interacts during the loading of the page, but not on drag. So the questions are a) how should I connect two surfaces with a spring and b) how do I update the physics when I drag one surface so that the other surface would follow the dragged surface?
The code I so far have is this
define(function(require) {
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var StateModifier = require('famous/modifiers/StateModifier');
var PhysicsEngine = require('famous/physics/PhysicsEngine');
var Circle = require('famous/physics/bodies/Circle');
var Draggable = require('famous/modifiers/Draggable');
var Spring = require('famous/physics/forces/Spring');
var Vector = require('famous/math/Vector');
var context = Engine.createContext();
var physicsEngine = new PhysicsEngine();
var ball = new Surface ({
size: [100,100],
properties: {
backgroundColor: 'red',
borderRadius: '50px'
}
});
var ball2 = new Surface ({
size: [100,100],
properties: {
backgroundColor: 'blue',
borderRadius: '50px'
}
});
var draggable = new Draggable();
var draggable2 = new Draggable();
ball.state = new StateModifier({origin:[0.2,0.2]});
ball2.state = new StateModifier({origin:[0.3,0.3]});
ball.particle = new Circle({radius:100});
ball2.particle = new Circle({radius:100});
var spring = new Spring({
anchor: ball.particle,
period: 400, // <= Play with these values :-)
dampingRatio: 0.07, // <=
length: 50
});
// var spring2 = new Spring({anchor: ball2.particle});
// physicsEngine.attach(spring, ball2.particle);
// physicsEngine.attach(spring2, ball.particle);
draggable.subscribe(ball);
draggable2.subscribe(ball2);
draggable.on('update', function() {
console.info('update');
ball2.particle.applyForce(new Vector(0, 0, -0.005 * 100));
// ball.state.setTransform(ball.particle.getTransform())
// ball.state.setTransform(ball.particle.getTransform())
// ball.particle.setVelocity([0.001,0,0]);
// physicsEngine.wake();
// physicsEngine.step();
});
draggable2.on('update', function() {
// ball2.particle.setVelocity([0.001,0,0]);
// console.info('update');
// physicsEngine.wake();
// physicsEngine.step();
});
physicsEngine.attach(spring, ball2.particle);
// spring.applyForce(ball.particle);
physicsEngine.addBody(ball.particle);
physicsEngine.addBody(ball2.particle);
// ball.on("click",function(){
// ball.particle.setVelocity([10,0,0]);
// });
//
// ball2.on("click",function(){
// ball2.particle.setVelocity([0,10,0]);
// });
context.add(draggable).add(ball.state).add(ball);
context.add(draggable2).add(ball2.state).add(ball2);
Engine.on('prerender', function(){
ball.state.setTransform(ball.particle.getTransform());
ball2.state.setTransform(ball2.particle.getTransform());
});
});
It seems like you have a pretty good understanding of the PE thus far. I can still see a few places you can improve. Here is a working example of dragging with a spring attached. Although this implementation is not perfect yet, it should get you started.. If you start with dragging the red circle, everything works as expected.. Draggable has its own position, and so does particle. So when you grab the blue circle, there remains an offset in particle. Here is what will get you 95%..
Hope it helps..
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface');
var Transform = require('famous/core/Transform');
var Modifier = require('famous/core/Modifier');
var Draggable = require('famous/modifiers/Draggable');
var PhysicsEngine = require('famous/physics/PhysicsEngine');
var Circle = require('famous/physics/bodies/Circle');
var Spring = require('famous/physics/forces/Spring');
var context = Engine.createContext();
var physicsEngine = new PhysicsEngine();
var ball = new Surface ({
size: [100,100],
properties: {
backgroundColor: 'red',
borderRadius: '50px'
}
});
var ball2 = new Surface ({
size: [100,100],
properties: {
backgroundColor: 'blue',
borderRadius: '50px',
}
});
ball.mod = new Modifier({origin:[0.5,0.5]});
ball.draggable = new Draggable();
ball.pipe(ball.draggable);
ball.particle = new Circle({radius:100});
ball.mod.transformFrom(function(){ return Transform.translate(0,0,0) });
ball.spring = new Spring({
anchor: ball.particle,
period: 400,
dampingRatio: 0.07,
length: 50
});
ball2.mod = new Modifier({origin:[0.5,0.5]});
ball2.draggable = new Draggable();
ball2.pipe(ball2.draggable);
ball2.particle = new Circle({radius:100});
ball2.mod.transformFrom(function(){ return ball2.particle.getTransform()});
ball2.spring = new Spring({
anchor: ball2.particle,
period: 400,
dampingRatio: 0.07,
length: 50
});
ball.draggable.on('start',function(){
ball2.setProperties({pointerEvents:'none'});
if (ball2.springID) physicsEngine.detach(ball2.springID);
if (ball.springID) physicsEngine.detach(ball.springID);
ball.springID = physicsEngine.attach(ball.spring, ball2.particle);
ball2.springID = null;
ball.mod.transformFrom(function(){ return Transform.translate(0,0,0) });
ball2.mod.transformFrom(function(){ return ball2.particle.getTransform()});
})
ball.draggable.on('update', function() {
pos = ball.draggable.getPosition();
ball.particle.setPosition(pos);
});
ball.draggable.on('end', function() {
ball2.setProperties({pointerEvents:'all'});
});
ball2.draggable.on('start',function(){
ball.setProperties({pointerEvents:'none'});
if (ball2.springID) physicsEngine.detach(ball2.springID);
if (ball.springID) physicsEngine.detach(ball.springID);
ball2.springID = physicsEngine.attach(ball2.spring, ball.particle);
ball.springID = null;
ball2.mod.transformFrom(function(){ return Transform.translate(0,0,0) });
ball.mod.transformFrom(function(){ return ball.particle.getTransform()});
})
ball2.draggable.on('update', function() {
pos = ball2.draggable.getPosition();
ball2.particle.setPosition(pos);
});
ball2.draggable.on('end', function() {
ball.setProperties({pointerEvents:'all'});
});
ball.springID = physicsEngine.attach(ball.spring, ball2.particle);
physicsEngine.addBody(ball.particle);
physicsEngine.addBody(ball2.particle);
context.add(ball.mod).add(ball.draggable).add(ball);
context.add(ball2.mod).add(ball2.draggable).add(ball2);