Markerclusterer set marker cluster icon depending on markers inside it - javascript

Let's say that I want to make a webpage that will show approximate number of free parking spots left on each parking(due to confidentiality I'm not allowed to give any details of what I'm actually doing). For this I'm using Google maps and Markerclusterer. So for parking with less than 5% spots left I'd like to show a red marker, for parkings with 5%-25% spots I'd show a yellow one and for ones with more than 25% free spots I want to make it green. So far I could make those markers and cluster them, but here's the tricky part(and question itself):
How can I make a cluster icon dependant on the markers inside it?
For example:
Parking A is green
Parking B is red
Parking C is green
Parking D is yellow
When zoomed out I want to show the cluster that has all 4 of them red(worst of all). When zoomed in I'd get 2 clusters(A+B and C+D). I want the first cluster(A+B) to be red and second(C+D) should be yellow.
What I did so far:
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/greenC.png",
width: 46
},
{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/redC.png",
width: 46
}]};
var markers = [];
for (var i = 0; i < 100; i++) {
var latLng = new google.maps.LatLng(51 + Math.random(),
4 + Math.random());
var marker = new google.maps.Marker({'position': latLng, icon: 'img/greenP.png'});
markers.push(marker);
}
for (var i = 0; i < 20; i++) {
var latLng = new google.maps.LatLng(51 - Math.random(),
4 - Math.random());
var marker = new google.maps.Marker({'position': latLng, icon: 'img/redP.png'});
markers.push(marker);
}
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);
Right now I only have red and green markers, which should be enough to test it. But behaviour of this cluster now is as follows:
All clusters with less than 10 markers in it are green
All clusters with more than 9 markers in it are red
EDIT
From this link I found that what I might need is Calculator. So I tried it, but still no luck(although I think that I'm getting closer. Actually, I hope I'm very close to the solution right now).
So I tried to change my options:
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/greenC.png",
width: 46
},
{
height: 46,
url: "///Users/Novarg/Downloads/foundation-5.4.7/img/redC.png",
width: 46
}],
calculator: function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("redP.png") > -1) {
return {text: markers.length, index: 1};
}
}
return {text: markers.length, index: 0};
}
};
But the Calculator is never being used. I tested it by putting a simple alert('test'); inside it.
I hope this additional info will help you to help me find a solution.

As I already mentioned in the edit, I was very close to the solution. So I took another(fresh) look at the code today, checked the docs once again and noticed the following in the ClusterIconInfo:
index number The index plus 1 of the element in the styles array to be used to style the cluster icon.
So basically I solved this problem simply by incrementing the index by one(and I also moved Calculator to be a var and then used setCalculator() method on the MarkerClusterer itself). So my code became:
var calc = function(markers, numStyles) {
for (var i = 0; i < markers.length; i++) {
if (markers[i].getIcon().indexOf("redP") > -1) {
return {text: markers.length, index: 2};
}
}
return {text: markers.length, index: 1};
}
var mcOptions = {gridSize: 50, maxZoom: 15, styles: [{
height: 46,
url: "img/greenC.png",
width: 46
},
{
height: 46,
url: "img/redC.png",
width: 46
}]
};
var markerCluster = new MarkerClusterer(map, markers, mcOptions);
markerCluster.setCalculator(calc);
And now it works like a charm(as it should).
Hopefully this could help somebody someday.

Extending on Novarg's answer, you will need to define a Calculator, which selects a style from the initial styles array passed into the MarkerClusterer's constructor.
If you want to extend the original functionality (which controls the style based on the number of markers the Cluster contains, you can add styles in multiples of 3 in the initial styles array and call the original Calculator in your calculator.
// create MarkerClusterer instance with array of styles, multiples of 3
const markerClusterer = new MarkerClusterer(this.map, this.kioskMarkers, {
clusterClass: "custom-clustericon",
styles: [
{
width: 30,
height: 30,
className: "normal"
},
{
width: 40,
height: 40,
className: "normal"
},
{
width: 50,
height: 50,
className: "normal"
},
{
width: 30,
height: 30,
className: "special"
},
{
width: 40,
height: 40,
className: "special"
},
{
width: 50,
height: 50,
className: "special"
}]
});
// your function that determines special styling
const checkForYourSpecialCondition = () => true;
// new calculator function
const newCalculator = (markers, numStyles) => {
const offset = checkForYourSpecialCondition() ? 3 : 0;
const { text, index } = MarkerCluster.CALCULATOR(markers, numStyles);
return {text, index: index + offset};
}
markerClusterer.setCalculator(newCalculator);

Related

Longitude and Latitude of polyline : Leaflet

Is their a way to get latitude and longitude of polyline?
var firstpolyline = new L.Polyline(pointList, {
color: 'black',
opacity: 5,
smoothFactor: 1,
weight: 3,
})
map.addLayer(firstpolyline);
firstpolyline.getLatLng();
here firstpolyline is givng an error that "getLatLng() is not a function". I want to check if polyline is within the map bound or not like this
var bounds = map.getBounds();
if(bounds.contains(firstpolyline.getLatLng())){
......
}
You have to use getLatLngs() function. So try this:
var firstpolyline = new L.Polyline(pointList, {
color: 'black',
opacity: 5,
smoothFactor: 1,
weight: 3,
})
map.addLayer(firstpolyline);
var arrayOfPoints = firstpolyline.getLatLngs();
Then you can easily iterate over an array of points and get latitude and logitude or check if point is in bounds of polygon.
for(var i=0; i < arrayOfPoints.length; i++) {
if(map.getBounds().contains(arrayOfPoints[i])) {
console.log('is in bounds');
};
}

Make Leafletmarkers searchable and blurr out the others

With help of the StackOverflow community I built a leaflet map with markers for blogdata and articledata. The blogdata represents the IDs and geoloations of newsrooms and the articledata are the locations from articles the newsrooms wrote. So there are several articles per newsroom and I connected those with polylines (see picture below).
What I'd like now to do is make that leaflet map searchable, not for cities or countries but for the newsrooms ID. And I'd like to manage blurring all the other markers and lines out and zooming to the searched blog and it's connected articles.
This is what I got so far:
function myFunction() {
var map = L.map('map').setView([51.101516, 10.313446], 6);
// improve experience on mobile
if (map.tap) map.tap.disable();
L.tileLayer('http://server.arcgisonline.com/ArcGIS/rest/services/Canvas/World_Light_Gray_Base/MapServer/tile/{z}/{y}/{x}', {
attribution: 'Tiles © Esri — Esri, DeLorme, NAVTEQ',
maxZoom: 16
}).addTo(map);
map._layersMinZoom=5;
var newsroomsById = {};
for(i=0; i<newsrooms.length; i++) {
newsroomsById[newsrooms[i].ID] = newsrooms[i];
}
for(i=0; i<articles.length; i++) {
// retrieve newsroom
var newsroom = newsroomsById[articles[i].ID];
// draw your polyline
var latlngs = [
[articles[i].lat, articles[i].long],
[newsroom.lat, newsroom.long]
];
var polyline = L.polyline(latlngs, {
color: 'grey',
weight: 2,
opacity: 0.5,
smoothFactor: 1,
}).addTo(map);
var room_marker = L.circleMarker([newsroom.lat, newsroom.long], {
radius: 3,
color: '#29D3A0',
fillColor: '#29D3A0',
fillOpacity: 1,
}).addTo(map);
room_marker.bindPopup("<strong style='color: #84b819'>Newsroom </strong>" + newsroom.ID + "<br>").openPopup();
var popup = L.popup();
var art_marker = L.circleMarker([articles[i].lat, articles[i].long], {
radius: 2,
color: '#000',
fillColor: '#000',
fillOpacity: 1,
}).addTo(map);
}
}
And this is how the map looks like (black is article, green is newsroom/blog)
EDIT:
To make the map searchable use the Leaflet plugin L.Search.Control
It's difficult to answer the search part of the question. I think you'll have to describe a use case for that.
However, once you have the ID of the newsroom you want to highlight, you can change the opacity of your polylines and circleMarkers using setOption
However, your code needs some adjustments: you need to keep an array of your markers and keep the ID of the newsrooms in the markers.
Another thing: you should not create newsroom markers in the article loop; it creates as many newsroom markers as your number of articles.
Here is a proposition (selection is made by clicking on the newsroom marker):
var selectedNewsroom = 0;
var newsroomsById = {};
// create newsroom markers
var newsroomMarkers = [];
for(i=0; i<newsrooms.length; i++) {
newsroomsById[newsrooms[i].ID] = newsrooms[i];
var room_marker = L.circleMarker([newsrooms[i].lat, newsrooms[i].long], {
radius: 20,
color: '#000',
opacity: .4,
fillOpacity: .4,
}).addTo(map);
//room_marker.bindPopup("<strong style='color: #84b819'>Newsroom </strong>" + newsrooms[i].ID + "<br>");
room_marker.ID = newsrooms[i].ID; // associate marker with newsroom
room_marker.on('click', function(e) {
console.log('clicked on ' + e.target.ID);
changeSelection(e.target.ID);
});
newsroomMarkers.push(room_marker); // keep marker reference for later
}
// create article markers and connections to newsrooms
var articleMarkers = [];
for(i=0; i<articles.length; i++) {
// retrieve newsroom
var newsroom = newsroomsById[articles[i].ID];
// draw your polyline
var latlngs = [
[articles[i].lat, articles[i].long],
[newsroom.lat, newsroom.long]
];
var polyline = L.polyline(latlngs, {
color: '#000',
weight: 1,
opacity: .4,
smoothFactor: 1,
}).addTo(map);
var art_marker = L.circleMarker([articles[i].lat, articles[i].long], {
radius: 2,
color: '#000',
fillColor: '#000',
opacity: .4,
fillOpacity: .4,
}).addTo(map);
art_marker.connection = polyline; // associate polyline with marker
art_marker.newsroomID = newsroom.ID;
articleMarkers.push(art_marker); // keep marker reference for later
}
// highlight or blur newsrooms base on which is selected
function changeSelection(newsroomID) {
// deselect everything
for(i=0; i<articleMarkers.length; i++) {
articleMarkers[i].setStyle({ opacity: .4, fillOpacity: .4 });
articleMarkers[i].connection.setStyle({ opacity: .4 });
}
for(i=0; i<newsroomMarkers.length; i++) {
newsroomMarkers[i].setStyle({ opacity: .4, fillOpacity: .4 });
}
if(selectedNewsroom == 0 || selectedNewsroom != newsroomID) {
selectedNewsroom = newsroomID;
for(i=0; i<articleMarkers.length; i++) {
if(articleMarkers[i].newsroomID == newsroomID) {
articleMarkers[i].setStyle({ opacity: 1, fillOpacity: 1 });
articleMarkers[i].connection.setStyle({ opacity: 1 });
}
}
for(i=0; i<newsroomMarkers.length; i++) {
if(newsroomMarkers[i].ID == newsroomID) {
newsroomMarkers[i].setStyle({ opacity: 1, fillOpacity: 1 });
}
}
}
else {
selectedNewsroom = 0;
}
}
And a working example.

Dynamically change Leaflet layer

First, to get some context, I asked this a few weeks ago.
I now have Leaflet parsing a folder of gpx files, and drawing all of them in one map instance, using the Omnivore plugin.
Now what I need is for those files to change dynamically. I have a slider that represents a time interval. Each time I use the slider, I narrow the interval in which I want the tracks to be shown.
Ex: My slider goes from 15th of January to the 15th of May. So, the map shows all tracks from that time. If I change the slider to show from the 1st of April to the 15th of May, the map should react accordingly and re-draw the corresponding files, erasing from the map the tracks from before the 1st of April.
The problem is that I can't seem to get it doing this, since I keep getting a
TypeError: Cannot read property 'addLayer' of undefined
When I make a console.log of the layer, in the line where the problem happens, it prints at exactly the same time, the layer and then an undefined. I don't know why this happens and it is very hard do debug.
My code is as follows:
setTimeout(function() {
var osmUrl = 'http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {maxZoom: 18, attributionControl: false});
var ggl = new L.Google();
angular.element($elem[0]).append(angular.element('<div id="trackmap'+ trackmapCount +'" style="width: 100%; height: calc(100% - 25px); border: 1px solid #ccc"></div>'));
trackmaps[trackmapCount] = new L.Map('trackmap'+ trackmapCount +'', {center: new L.LatLng(center[0], center[1]), zoom: 10});
trackmaps[trackmapCount].addControl(new L.Control.Layers( {'Google':ggl,'OSM':osm}, {}));
console.log('map'+ trackmapCount +'');
var layer1 = osm.addTo(trackmaps[trackmapCount]);
createTracks(jsonRes);
$scope.$watch(function () {
return $elem[0].parentNode.clientWidth;
}, function ( w ) {
if ( !w ) { return; }
for(var i = 0; i < trackmapCount; i++) {
trackmaps[i].invalidateSize();
}
});
$scope.$watch(function () {
return $elem[0].parentNode.clientHeight;
}, function ( h ) {
if ( !h ) { return; }
for(var i = 0; i < trackmapCount; i++) {
trackmaps[i].invalidateSize();
}
});
$scope.$on('$destroy', function() {
rootScopeBroadcast();
});
var rootScopeBroadcast = $rootScope.$on('rootScope:broadcast-timeline_slider', function (event, data) {
for(var i = 0; i < trackmapCount; i++) {
trackmaps[i].removeLayer(runLayer);
}
var new_tracks = sliderProcessing(data.min_time, data.max_time, jsonRes)
createTracks(new_tracks); // THE PROBLEM IS HERE
});
function createTracks (track_list) {
myStyle = {
"color": "#0033ff",
"weight": 5,
"opacity": 0.65,
"clickable": true
};
customLayer = L.geoJson(null, {
style: myStyle,
});
for (var i = 0; i < track_list.length; i += 1) {
runLayer = omnivore.gpx(folderPath + track_list[i], null, customLayer)
.on('ready', function() {
//runLayer.showExtremities('arrowM');
})
.addTo(trackmaps[trackmapCount])
//a console.log of trackmaps[trackmapCount] here shows both the layer and an undefined at the same time
.on('click', function(d) {
console.log(d);
});
}
}
trackmapCount++;
delay = 0;
}, delay);
I'm probably missing some Leaflet re-draw function, or my control code is not correctly placed, but I've messed around with it and it stays the same.

Adding elements in bulk on JointJS leads to performance issues

We are trying to build a Visualization Framework using JointJS (we are not using Rappid). SVG is used to render graphical data on the browser.
We have a scenario where we have a model with approximately 4500 ports.
We have used ports as elements and NOT as attributes. Meaning to say that we have embedded ports as cells inside a model. There is a certain reason why need such a setup.
When such a model is added onto the graph, it takes roughly 8 to 9 seconds to render. Once the model is rendered, moving and repositioning the model works okay. However, when we try to add further models the browser tends to crash.
This is how we have created the Paper object
function createPaper(options) {
var paper = new joint.dia.Paper({
el: options.el,
width: options.width,
height: options.height,
model: options.graph,
gridSize: 1,
label:options.name,
async : { batchSize: 250}
});
return paper;
}
This is how we have created the model
function createModel(options) {
var model = new joint.shapes.devs.Model({
position:options.position,
size:options.size,
attrs: options.attrs
});
return model;
}
This is how we have created the port as a custom model
joint.shapes.custom = {};
joint.shapes.custom.Port = joint.shapes.basic.Generic.extend(_.extend({}, joint.shapes.basic.PortsModelInterface, {
markup: '<g class="rotatable"><g class="scalable"><path class="port-body" d = "M 0 0 L 0 0 L 15 5 L 0 10 L 0 10 z"></path></g><text class="port-label"/></g>',
defaults: joint.util.deepSupplement({
type: 'devs.Port',
size: { width: 10, height: 10 },
position : {x : 0, y : 0},
attrs: {
'.': { magnet: false },
'.body': {
width: 150, height: 250,
stroke: '#000000'
},
'.port-body': {
r: 5,
magnet: true,
stroke: '#000000',
fill : 'yellow'
},
text: {
'pointer-events': 'none'
},
}
}, joint.shapes.basic.Generic.prototype.defaults),
}));
joint.shapes.custom.PortView = joint.dia.ElementView.extend(joint.shapes.basic.PortsViewInterface);
function createPort(options){
var port=new joint.shapes.custom.Port({
position:options.position,
size:options.size,
attrs: options.attrs
});
return port;
}
Below is the way we are adding models and ports to the graph
var cells = [];
var model = createModel(options);
//Add the cell to the graph
cells.push(model);
var totalInports=options.inPorts.length;
var totalOutports=options.outPorts.length;
//Add the inports
for (var portIndex = 0; portIndex < totalInports; portIndex++) {
port = createPort(options.inPorts[portIndex]);
model.embed(port);
cells.push(port);
}
//Add the outports
for (var portIndex = 0; portIndex < totalOutports; portIndex++) {
port = createPort(options.outPorts[portIndex]);
model.embed(port);
cells.push(port);
}
graph.addCells(cells);
Could anybody kindly give some answers to the below questions -
Are we using the paper and models in the right way ?
Are there better ways of adding cells to the graph which could improve the
performance ?
Thanks in Advance
I know that this is an old question that has been left unanswered for a long time. My take is to use:
graph.fromJSON or graph.addCells(/*Array of json cells*/)
Then, try to translate all the operations as a compound json that will be passed to graph.fromJSON or graph.addCells.

Point in Polygon using leaflet-pip

I'm trying to, given a .json containing a lot of points, determine how many there are in each region (probably returning a dictionary), which are defined in another .json file.
I'm doing this based on this example:
https://www.mapbox.com/mapbox.js/example/v1.0.0/point-in-polygon/
However, I can't get it to work.
This line:
var layer = leafletPip.pointInLayer(this.getLatLng(), states, true);
Returns empty for my test case.
Here is a jsfiddle reproducing my code:
http://jsfiddle.net/Pe5xU/346/
map = L.map('map').setView([40.658528, -73.952551], 10);
// Load a tile layer
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap',
maxZoom: 18,
minZoom: 10
}).addTo(map);
geojson = L.geoJson(data).addTo(map);
var all_markers = [];
var layers = {};
$.each(dots, function(index, rec) {
var markers = {}
if (rec.hasOwnProperty("latitude") && rec.hasOwnProperty("longitude")) {
var marker = L.circleMarker([rec.latitude, rec.longitude], marker_style()).addTo(map);
all_markers.push(marker);
}
});
var all_layers = L.featureGroup(all_markers);
map.fitBounds(all_layers.getBounds());
function marker_style() {
return {
radius: 4,
weight: 0,
opacity: 1,
color: 'white',
dashArray: '3',
fillOpacity: 0.7
};
}
$.each(dots, function(index, rec) {
if (rec.hasOwnProperty("latitude") && rec.hasOwnProperty("longitude")) {
var layer = leafletPip.pointInLayer([rec.latitude, rec.longitude], geojson, true);
console.log(layer);
}
});
This code example provides coordinates in latitude, longitude order. As documented in the leaflet-pip readme, leaflet-pip expects coordinates in longitude, latitude order, the same as GeoJSON and other geospatial formats.

Categories

Resources