Openlayer-3 : Draw a polyline with GPS coordinates - javascript

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 );

Related

Draw lines between coordinates from JSON file

I have a JSON that holds the data:
var myData = [
{
"ID": 1,
"DeviceSerialNumber": "941",
"Latitude": 64.19219207763672,
"Longitude": 28.963903427124023,
},
{
"ID": 2,
"DeviceSerialNumber": "9416",
"Latitude": 65.34219207763672,
"Longitude": 29.543903427124023,
},
{
"ID": 3,
"DeviceSerialNumber": "9416ba6",
"Latitude": 66.34219207763672,
"Longitude": 28.543903427124023,
},
]
I am able to draw points on map with following code:
function loadData(data) {
var adataSource = new ol.source.Vector();
// transform the geometries into the view's projection
var transform = ol.proj.getTransform('EPSG:4326', 'EPSG:3857');
// loop over the items in the response
for (var i = 0; i < data.length; i++) {
//create a new feature with the item as the properties
var feature = new ol.Feature(data[i]);
// add a name property for later ease of access
feature.set('name', data[i].DeviceSerialNumber);
// create an appropriate geometry and add it to the feature
var coordinate = transform([parseFloat(data[i].Longitude), parseFloat(data[i].Latitude)]);
var geometry = new ol.geom.Point(coordinate);
feature.setGeometry(geometry);
// add the feature to the source
dataSource.addFeature(feature);
}
return dataSource;
}
var vectorLayer = new ol.layer.Vector({
source: loadData(myData),
visible: true,
style: new ol.style.Style({
image: new ol.style.Circle( /** #type {olx.style.IconOptions} */({
radius: 5,
fill: new ol.style.Fill({
color: '#266ca6'
})
}))
})
});
However I would like to draw a route = lines, based on coordinates. So that route would be drawn starting from first coordinate and then until next coordinate, then next coordinate and so on... I have tried to modify my code this way, but I am not getting any routes on my map. What I am doing wrong?
//loads data from a json file and creates features
function loadData(data) {
var dataSource = new ol.source.Vector();
// transform the geometries into the view's projection
var transform = ol.proj.getTransform('EPSG:4326', 'EPSG:3857');
// loop over the items in the response
for (var i = 0; i < data.length; i++) {
//create a new feature with the item as the properties
var feature = new ol.Feature(data[i]);
// add a name property for later ease of access
feature.set('name', data[i].DeviceSerialNumber);
// create an appropriate geometry and add it to the feature
var coordinate = transform([parseFloat(data[i].Longitude), parseFloat(data[i].Latitude)]);
var geometry = new ol.geom.LineString(coordinate);
feature.setGeometry(geometry);
// add the feature to the source
dataSource.addFeature(feature);
}
return dataSource;
}
var vectorLayer = new ol.layer.Vector({
source: loadData(myData),
visible: true,
style: new ol.style.Style({
fill: new ol.style.Fill({ color: '#00FF00', weight: 4 }),
stroke: new ol.style.Stroke({ color: '#00FF00', width: 2 })
})
});
Here is an example what I have been trying to use: Draw line between two points using OpenLayers
A LineString will need a single feature containing all the coordinates
var coordinates = [];
for (var i = 0; i < data.length; i++) {
var coordinate = transform([parseFloat(data[i].Longitude), parseFloat(data[i].Latitude)]);
coordinates.push(coordinate);
}
var feature = new ol.Feature();
var geometry = new ol.geom.LineString(coordinates);
feature.setGeometry(geometry);
// add the feature to the source
dataSource.addFeature(feature);

OperLayers: display markers while fetching them

I'm fetching via ajax requests a few thousands points to display on an OSM map.
I get 100 points for each call.
I would like them to show up on the map as soon as they are fetched, while now they show up only when all the calls (around 20) are done.
Here is my code:
var source = new ol.source.OSM();
var map_layer = new ol.layer.Tile({
source: source
});
var map_view = new ol.View({
center: ol.proj.fromLonLat([12, 44]),
zoom: 9
});
var map = new ol.Map({
layers: [map_layer, ],
target: 'map-canvas',
view: map_view
});
var target = $(map.getTargetElement());
target.width(window.innerWidth);
target.height(window.innerHeight * 0.9);
map.updateSize();
var container = document.getElementById('popup');
var content = document.getElementById('popup-content');
var closer = document.getElementById('popup-closer');
var overlay = new ol.Overlay({
element: container,
autoPan: true,
autoPanAnimation: {
duration: 250
}
});
map.addOverlay(overlay);
closer.onclick = function() {
overlay.setPosition(undefined);
closer.blur();
return false;
};
var markers = new ol.Collection([], {unique: true})
var vectors = new ol.source.Vector({
features: markers
});
var style = new ol.style.Style({
image: new ol.style.Icon({
anchor: [0.5, 1],
src: 'pin.jpg'
})
});
var layer = new ol.layer.Vector({
source: vectors,
style: style,
});
map.addLayer(layer);
var data = [];
var url = 'some_url';
while (url) {
$.ajax({
async: false,
url: url
}).done(function(data) {
url = data.next;
var res = data.results;
for (var i=0; i<res.length; i++) {
if (res[i].latitude & res[i].longitude) {
var point = ol.proj.fromLonLat([res[i].longitude, res[i].latitude]);
var feature = new ol.Feature({
geometry: new ol.geom.Point(point),
latitude: res[i].latitude,
longitude: res[i].longitude,
})
markers.extend([feature, ]);
}
}
map.render();
});
}
Last instruction you can see (map.render()) shouldn't do the trick? It isn't.
map.renderSync() isn't helping too.
It would be perfect if it could start rendering the map tiles while fetching those points, now even tiles are rendered after fetching is complete.
Looks like the good strategy here is recursion. Simply doing async calls isn't enough.
While each call is async (this letting the map start rendering as soon as possible) the next call is done after showing markers from the previous one.
var url = 'some_url';
var limit = 100;
var offset = 0;
var total_count = 2000;
fetch_data(offset);
function fetch_data(offset) {
$.ajax({
url: encodeURI(url + '?limit=' + limit + '&offset=' + offset)
}).done(function(data) {
var res = data.results;
for (var i=0; i<res.length; i++) {
if (res[i].latitude & res[i].longitude) {
var point = ol.proj.fromLonLat([res[i].longitude, res[i].latitude]);
var feature = new ol.Feature({
geometry: new ol.geom.Point(point),
latitude: res[i].latitude,
longitude: res[i].longitude,
})
markers.extend([feature, ]);
}
}
offset += limit;
if (offset < total_count) {
fetch_data(offset);
}
});
}

Adding image along with animated linestring

I am trying to add an image/icon style along the lineString which is animating,but image/icon style is not getting added.
Here is my style for line
var lineStyle = new ol.style.Style({
image: new ol.style.Icon(({
opacity: 1,
size: 20,
src: '/Images/Leaflet.custom.marker/red_animation_marker.png'
})),
stroke: new ol.style.Stroke({
color: 'black',
width: 5,
lineDash: [10,5],
})
});
Refer following link animating line for current output.
What I am trying to achieve is, there should be a marker or pointer on the head of line which will indicate that line is following certain path, an arrow icon or style.
You need a separate style for the icon which includes a geometry (which will be the current point on the line). You might also want to calculate the angle to align your icon based on that and the previous point.
var blue = new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: 'blue'
})
});
var red = new ol.style.Style({
stroke: new ol.style.Stroke({
width: 2,
color: 'red'
})
});
var style = function(feature) {
var styles = [red];
coords = feature.getGeometry().getCoordinates().slice(-2);
styles.push(
new ol.style.Style({
geometry: new ol.geom.Point(coords[1]),
image: new ol.style.Icon({
src: 'https://cdn1.iconfinder.com/data/icons/basic-ui-elements-color-round/3/19-32.png',
rotation: Math.atan2(coords[1][0]-coords[0][0], coords[1][1]-coords[0][1])
})
})
)
return styles;
}
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var vector = new ol.layer.Vector({
source: new ol.source.Vector(),
style: blue
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View()
});
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://raw.githubusercontent.com/IvanSanchez/Leaflet.Polyline.SnakeAnim/master/route.js');
xhr.onload = function() {
// read the route coordinates
eval(xhr.responseText);
// reverse the route
var geom = new ol.geom.LineString(route.reverse());
// change Lat/Lon to Lon/Lat
geom.applyTransform(function(c){ return c.reverse(); });
geom.transform('EPSG:4326', map.getView().getProjection());
vector.getSource().addFeature(new ol.Feature(geom));
map.getView().fit(geom, { size: map.getSize() });
var snake = new ol.Feature();
snake.setStyle(style);
vector.getSource().addFeature(snake);
var snakeKey = animate_line(snake, geom, 30000, red);
map.once('singleclick', function() {
ol.Observable.unByKey(snakeKey);
vector.getSource().removeFeature(snake);
});
}
xhr.send();
function animate_line(feature, linestring, duration, completionStyle) {
var length = linestring.getLength();
var length_shown = 0;
var coords = linestring.getCoordinates();
var coords_shown = [coords[0], coords[0]];
var geom_shown = new ol.geom.LineString(coords_shown);
feature.setGeometry(geom_shown);
var coordcount = 1;
var start = new Date().getTime();
var listenerKey = map.on('postcompose', animate);
function animate() {
var elapsed = new Date().getTime() - start;
var toAdd = length*elapsed/duration - length_shown;
var point = linestring.getCoordinateAt(Math.min(elapsed/duration, 1));
// restart from last intermediate point and remove it
var newPart = new ol.geom.LineString(coords_shown.slice(-1));
coords_shown.pop();
// add vertices until required length exceeded
while (coordcount < coords.length && newPart.getLength() <= toAdd) {
newPart.appendCoordinate(coords[coordcount]);
coords_shown.push(coords[coordcount]);
coordcount++;
}
// replace overrun vertex with intermediate point
coords_shown.pop();
coordcount--;
coords_shown.push(point);
geom_shown.setCoordinates(coords_shown);
length_shown += toAdd;
if (elapsed > duration) {
feature.setStyle(completionStyle);
ol.Observable.unByKey(listenerKey);
}
map.render();
}
return listenerKey;
}
/*
draw = new ol.interaction.Draw({
source: vector.getSource(),
type: 'LineString'
});
draw.on('drawend',function(evt){
geom = evt.feature.getGeometry();
evt.feature.setGeometry(undefined);
animate_line(evt.feature, geom, 6000);
});
map.addInteraction(draw);
*/
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>
Same snippet plus some polygons:
var blue = new ol.style.Style({
stroke: new ol.style.Stroke({
width: 6,
color: 'blue'
})
});
var red = new ol.style.Style({
stroke: new ol.style.Stroke({
width: 2,
color: 'red'
})
});
var style = function(feature) {
var styles = [red];
coords = feature.getGeometry().getCoordinates().slice(-2);
styles.push(
new ol.style.Style({
geometry: new ol.geom.Point(coords[1]),
image: new ol.style.Icon({
src: 'https://cdn1.iconfinder.com/data/icons/basic-ui-elements-color-round/3/19-32.png',
rotation: Math.atan2(coords[1][0]-coords[0][0], coords[1][1]-coords[0][1])
})
})
)
return styles;
}
var raster = new ol.layer.Tile({
source: new ol.source.OSM()
});
var vector = new ol.layer.Vector({
source: new ol.source.Vector(),
style: function(feature) {
if (feature.getGeometry().getType() == 'Polygon') {
return feature.get('reached') ? red : [];
}
return blue
}
});
var map = new ol.Map({
layers: [raster, vector],
target: 'map',
view: new ol.View()
});
var poly = new ol.geom.Polygon([[[0,40], [10,60], [20,40], [10,20], [0,40]]]);
var polygons = [];
for (var i=0; i<4; i++) {
var poly2 = poly.clone();
poly2.translate(i*30, 0);
polygons.push(new ol.Feature(poly2.transform('EPSG:4326', map.getView().getProjection())));
}
vector.getSource().addFeatures(polygons);
var xhr = new XMLHttpRequest();
xhr.open('GET', 'https://raw.githubusercontent.com/IvanSanchez/Leaflet.Polyline.SnakeAnim/master/route.js');
xhr.onload = function() {
// read the route coordinates
eval(xhr.responseText);
// reverse the route
var geom = new ol.geom.LineString(route.reverse());
// change Lat/Lon to Lon/Lat
geom.applyTransform(function(c){ return c.reverse(); });
geom.transform('EPSG:4326', map.getView().getProjection());
vector.getSource().addFeature(new ol.Feature(geom));
map.getView().fit(geom, { size: map.getSize() });
var snake = new ol.Feature();
snake.setStyle(style);
vector.getSource().addFeature(snake);
var snakeKey = animate_line(snake, geom, 30000, red);
map.once('singleclick', function() {
ol.Observable.unByKey(snakeKey);
vector.getSource().removeFeature(snake);
});
}
xhr.send();
function animate_line(feature, linestring, duration, completionStyle) {
var length = linestring.getLength();
var length_shown = 0;
var coords = linestring.getCoordinates();
var coords_shown = [coords[0], coords[0]];
var geom_shown = new ol.geom.LineString(coords_shown);
feature.setGeometry(geom_shown);
var coordcount = 1;
var start = new Date().getTime();
var listenerKey = map.on('postcompose', animate);
function animate() {
var elapsed = new Date().getTime() - start;
var toAdd = length*elapsed/duration - length_shown;
var point = linestring.getCoordinateAt(Math.min(elapsed/duration, 1));
for (var i=0; i<polygons.length; i++) {
if (!polygons[i].get('reached') && polygons[i].getGeometry().intersectsCoordinate(point)) {
polygons[i].set('reached', true);
}
}
// restart from last intermediate point and remove it
var newPart = new ol.geom.LineString(coords_shown.slice(-1));
coords_shown.pop();
// add vertices until required length exceeded
while (coordcount < coords.length && newPart.getLength() <= toAdd) {
newPart.appendCoordinate(coords[coordcount]);
coords_shown.push(coords[coordcount]);
coordcount++;
}
// replace overrun vertex with intermediate point
coords_shown.pop();
coordcount--;
coords_shown.push(point);
geom_shown.setCoordinates(coords_shown);
length_shown += toAdd;
if (elapsed > duration) {
feature.setStyle(completionStyle);
ol.Observable.unByKey(listenerKey);
}
map.render();
}
return listenerKey;
}
html, body, .map {
margin: 0;
padding: 0;
width: 100%;
height: 100%;
}
<link href="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/css/ol.css" rel="stylesheet" />
<script src="https://cdn.rawgit.com/openlayers/openlayers.github.io/master/en/v5.3.0/build/ol.js"></script>
<div id="map" class="map"></div>

How to add 2 OpenLayer Marker Animations to page? Clickevent seems to be overwritten

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>

OpenLayer 3 - modify features of Cluster layer

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.

Categories

Resources