OperLayers: display markers while fetching them - javascript

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

Related

How to assign text from object to marker with same index and show relevant text after click to relevant marker? (Openlayers map)

I use OpenLayers map and I would like to get some text from var informations after click to marker. I think thant I need to have "general" function about singleclick in cycle for, because I need do it for each index [i]. But If I click to some marker I don´t see any information.
I tried to move "general" function to down before console.table(window.phpdata.markers). When I run it, I can see last text from "informations" after click on each marker --> there isn´t any problem with getting data from databases.
(This isn´t required result because I don´t want to see last marker. I would like to see relevant text from informations for relevant marker. So with same index [i].
What to do for this result? Thank´s
Object.defineProperty(window.phpdata, "markers", {value: <?php echo !empty($list) ? json_encode($list) : 'null'; ?>, writable: false, configurable: false});
var base = new ol.layer.Tile({
source: new ol.source.OSM()
});
var map = new ol.Map({
target: 'map',
layers: [base],
view: new ol.View({
center: ol.proj.fromLonLat([-74.0061,40.712]), zoom: 2
})
});
var vectors = new ol.source.Vector({});
if(window.phpdata.markers && window.phpdata.markers.length > 0) {
var data = window.phpdata.markers;
for(var i = 0; i < data.length; i++) {
var informations = data[i].info;
var marker = new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.fromLonLat([Number(data[i].lng), Number(data[i].lat)])
),
});
marker.setStyle(new ol.style.Style({
image: new ol.style.Icon(({
src: 'dot.png'
}))
}));
vectors.addFeature(marker);
marker.on('singleclick', function (event) { //general function to move..
if (map.hasFeatureAtPixel(event.pixel) === true) {
document.getElementById("www").innerHTML=informations;
} else {
overlay.setPosition(undefined);
closer.blur();
}
}); // //to there
}
var layer = new ol.layer.Vector({
source: vectors,
});
map.addLayer(layer);
}
document.getElementById('myposition').innerText = "click to map and get coordinates here";
map.on('singleclick', event => {
const coordinate = ol.proj.toLonLat(event.coordinate);
//const innerText = `Lon: ${coordinate[0].toFixed(4)}, Lat: ${coordinate[1].toFixed(4)}`;
const innerText = `${coordinate[0].toFixed(4)}, ${coordinate[1].toFixed(4)}`;
document.getElementById('myposition').innerText = innerText;
});
map.on('pointermove', function(evt) {
map.getTargetElement().style.cursor = map.hasFeatureAtPixel(evt.pixel) ? 'pointer' : '';
});
console.table(window.phpdata.markers)
Features do not have click events. You need to check if there are features where you click the map. You can add the info as a property to the feature and use get() to display it.
for(var i = 0; i < data.length; i++) {
var marker = new ol.Feature({
geometry: new ol.geom.Point(
ol.proj.fromLonLat([Number(data[i].lng), Number(data[i].lat)])
),
info: data[i].info
});
}
....
....
map.on('click', function(evt) {
var feature = map.forEachFeatureAtPixel(evt.pixel,
function(feature) {
return feature;
});
if (feature) {
document.getElementById("www").innerHTML = feature.get('info');
....
};
});

Openlayer-3 : Draw a polyline with GPS coordinates

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

returning object from a javascript function with database query inside

I am trying to return the markers as the object but when i run the function it just returns [ ], but printing it inside i can see the object data, can anyone explain how to return the object batch2 please?
google.maps.event.addListener(mgr, 'loaded', function(){
mgr.addMarkers(getMarkers(),6); //add all the markers! documentation for viewports with totals for city count, look at viewport
mgr.addMarkers(getMarkers2(),14); //get markers for zoomed out place, add click function to zoom in
//mgr.addMarkers(getMarkers(1000), 8);
console.log("added");
mgr.refresh();
});
function getMarkers2() {
var batch2 = [];
var clusters = new Parse.Query("cityfreqcoords");
var clusterresults = new Parse.Object("cityfreqcoords");
clusters.find({
success: function (results) {
for (i = 1; i < results.length; i++) {
var city = (results[i]["attributes"]["city"]);
var count = (results[i]["attributes"]["count"]);
var lat = (results[i]["attributes"]["lat"]);
var lng = (results[i]["attributes"]["lng"]);
var markerLatlong = new google.maps.LatLng(lat, lng);
//icon =
//adding the marker
var marker2 = new google.maps.Marker({
position: markerLatlong,
title: city,
clickable: true,
animation: google.maps.Animation.DROP
//icon:icon
});
//adding the click event and info window
google.maps.event.addListener(marker2, 'click', function () {
map.setZoom(6);
map.setCenter(marker2.getPosition());
});
batch2.push(marker2);
}
}
})
return batch2;
}
It would appear that clusters.find is asynchronous. You return batch2 before cluster.find succeeds. There are a handful of patterns for working with asynchronous code in JavaScript -- one common one is to use a callback. You would need to rewrite your code like so:
function getMarkers2(callback) {
var batch2 = [];
var clusters = new Parse.Query("cityfreqcoords");
var clusterresults = new Parse.Object("cityfreqcoords");
clusters.find({
success: function (results) {
for (i = 1; i < results.length; i++) {
var city = (results[i]["attributes"]["city"]);
var count = (results[i]["attributes"]["count"]);
var lat = (results[i]["attributes"]["lat"]);
var lng = (results[i]["attributes"]["lng"]);
var markerLatlong = new google.maps.LatLng(lat, lng);
//icon =
//adding the marker
var marker2 = new google.maps.Marker({
position: markerLatlong,
title: city,
clickable: true,
animation: google.maps.Animation.DROP
//icon:icon
});
//adding the click event and info window
google.maps.event.addListener(marker2, 'click', function () {
map.setZoom(6);
map.setCenter(marker2.getPosition());
});
batch2.push(marker2);
}
}
callback(batch2);
})
}
Then call it like so:
getMarkers2(function(markers) {
mgr.addMarkers(markers, 14);
});
If you're interested, take a look at how promises work as you might prefer that approach over using callbacks.
With callbacks in javascript, you generally don't return data. You pass in another function reference into the handler as a callback.
EG:
function getMarkers2(f) {
// do stuff
//when done
f(batch2)
}
Ended up just passing making the marker manager global and passing mgr into the query which worked, probably not the most efficient way to do it
function getMarkers2(mgr) {
Parse.initialize("X", "Y");
var batch2 = [];
var clusters = new Parse.Query("cityfrequency2");
var clusterresults = new Parse.Object("cityfrequency2");
clusters.find({
success: function (results) {
for (i = 0; i < (results.length); i++) {
var city = (results[i]["attributes"]["city"]);
var lat = (results[i]["attributes"]["lat"]);
var lng = (results[i]["attributes"]["lng"]);
var markerLatlong = new google.maps.LatLng(lat, lng);
var image = {
url: 'warning.png',
size: new google.maps.Size(50, 46),
// The origin
origin: new google.maps.Point(0, 0),
// The anchor
anchor: new google.maps.Point(25, 0)
};
//adding the marker
var marker2 = new google.maps.Marker({
position: markerLatlong,
title: city,
clickable: true,
animation: google.maps.Animation.DROP,
icon:image
});
//adding the click event and info window
google.maps.event.addListener(marker2, 'click', function () {
map.setZoom(6);
map.setCenter();
});
batch2.push(marker2);
mgr.addMarkers(batch2,0,6);
mgr.refresh();
}
}
})
}
function setupMarkers() {
var mgrOptions = { borderPadding: 50, maxZoom: 15, trackMarkers: true };
mgr = new MarkerManager(map,mgrOptions);
google.maps.event.addListener(mgr, 'loaded', function(){
getMarkers2(mgr);
getMarkers(mgr);
console.log("added");
});
}

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.

OL3: GetFeature from Layers by Coordinate

I want to get the Feature of a Layer by coordinate.
Furthermore I want to open this feature in a popup, which I have solved so far by an onclick event. But I want to realize by giving the coordinates of a feature and opening the popup of the featue.
I have a layer with the map and a layer with the features:
if (trackMap != null) {
for (var i = 0; i < trackMap.length; i++) {
var trackInfo = trackMap[i];
lat = parseFloat(trackInfo.lat);
lon = parseFloat(trackInfo.lon);
var layergpx = new ol.layer.Vector({
source: new ol.source.Vector({
parser: new ol.parser.GPX(),
url: '${contextRoot}/gps/gpx2' + trackInfo.url
})
});
layers.push(layergpx);
}
}
I want to get the feature of this layer in another Javascript function.
How I open a pop up by clicking on the map:
/**
* The Click Event to show the data
*/
var element = document.getElementById('popup');
var popup = new ol.Overlay({
element: element,
positioning: ol.OverlayPositioning.BOTTOM_CENTER,
stopEvent: false
});
map.addOverlay(popup);
map.on('singleclick', function(evt) {
map.getFeatures({
pixel: evt.getPixel(),
layers: vectorLayers,
success: function(layerFeatures) {
var feature = layerFeatures[0][0];
if (feature) {
var geometry = feature.getGeometry();
var coord = geometry.getCoordinates();
popup.setPosition(coord);
$(element).popover({
'placement': 'top',
'html': true,
'content': feature.get('desc')
});
$(element).popover('show');
} else {
$(element).popover('destroy');
}
}
});
});
But I want this feature not to be opened by clicking on it on the map, but by entering a coordinate in a textfield and the map opens this pop up, like in the onclick event.
Take a look at this example to see if it helps you:
http://openlayers.org/en/latest/examples/kml.html
var displayFeatureInfo = function(pixel) {
map.getFeatures({
pixel: pixel,
layers: [vector],
success: function(featuresByLayer) {
var features = featuresByLayer[0];
var info = [];
for (var i = 0, ii = features.length; i < ii; ++i) {
info.push(features[i].get('name'));
}
document.getElementById('info').innerHTML = info.join(', ') || '&nbsp';
}
});
map.getFeatures() has this success callback where it delivers the features of the layers specified in layers: [vector]. Customize it at will to get what you need.
=== Update ===
In the OpenLayers 3's Map object you have a function: getPixelFromCoordinate
/**
* #param {ol.Coordinate} coordinate Coordinate.
* #return {ol.Pixel} Pixel.
*/
ol.Map.prototype.getPixelFromCoordinate = function(coordinate) {
var frameState = this.frameState_;
if (goog.isNull(frameState)) {
return null;
} else {
var vec2 = coordinate.slice(0, 2);
return ol.vec.Mat4.multVec2(frameState.coordinateToPixelMatrix, vec2, vec2);
}
};

Categories

Resources