I've managed to cluster my markers. What I want to do now is to display a custom icon with the number of points in the cluster, but I can't figure out how to do that or if it's even possible.
I read the documentation and understood that I need to implement my own iconCreateFunction when creating the marker cluster.
addSomeMarkers: function(data, markerProperties) {
var markers = L.markerClusterGroup({
iconCreateFunction: function(cluster) {
// TODO
}
});
....
}
I know I can return L.divIcon with a custom css class and cluster.getChildCount(), but I can't specify markerProperties.iconUrl as an image that should be displayed.
I could also use L.icon with my custom icon from markerProperties.iconUrl, but in that case I don't see how I should get cluster.getChildCount() to display.
So what I need is a combination of both. Is there anything like that? And if not, can someone hint a workaround to achieve this?
Using the example here: https://github.com/Leaflet/Leaflet.markercluster/blob/master/example/marker-clustering-custom.html
And the documentation of L.divIcon is here:
http://leafletjs.com/reference.html#divicon
I came up with this example: http://franceimage.github.io/leaflet/8/?map=46.566414,2.4609375,6
Hopefully it will help you
Meaningful parts are:
var markerCluster = new L.MarkerClusterGroup({
iconCreateFunction: function (cluster) {
var markers = cluster.getAllChildMarkers();
var html = '<div class="circle">' + markers.length + '</div>';
return L.divIcon({ html: html, className: 'mycluster', iconSize: L.point(32, 32) });
},
spiderfyOnMaxZoom: false, showCoverageOnHover: true, zoomToBoundsOnClick: false
});
and css
.circle {
width: 32px;
height: 32px;
line-height: 32px;
background-image: url('circle.png');
text-align: center;
}
There may be other ways ...
Related
I'm using MapBox GL JS to create a map with a custom marker:
var marker = new mapboxgl.Marker(container)
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.addTo(map);
However, I seem to have some kind of offset problem with the marker. The thing is: when zoomed out a bit, the bottom of the marker is not really pointing to the exact location:
When I'm zooming in a bit further it reaches its destination and it's pointing to the exact spot.
I really love MapBox GL, but this particular problem is bugging me and I'd love to know how to solve it. When this is fixed my implementation is far more superior to the original mapping software I was using.
From Mapbox GL JS 0.22.0 you're able to set an offset option to the marker. https://www.mapbox.com/mapbox-gl-js/api/#Marker
For example to offset the marker so that it's anchor is the middle bottom (for your pin marker) you would use:
var marker = new mapboxgl.Marker(container, {
offset: [-width / 2, -height]
})
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.addTo(map);
New solution for mapbox-gl.js v1.0.0 - Marker objects now have an anchor option to set the position to align to the marker's Lat/Lng: https://docs.mapbox.com/mapbox-gl-js/api/#marker
var marker = new mapboxgl.Marker(container, {anchor: 'bottom');
This should cover most cases and is more reliable than a pixel offset in my experience.
I've found an solution to my problem. It might be somewhat hacky, but it solves the positioning problem of the marker: I'm using a Popup fill it with a font awesome map marker icon and remove it's "tooltip styled" borders:
Javascript:
map.on('load', function() {
var container = document.createElement('div');
var icon = document.createElement('i');
icon.dataset.city = city;
icon.addEventListener('click', function(e) {
var city = e.target.dataset.city;
var country = e.target.dataset.country
flyTo(datacenters[country][city].coordinates);
});
icon.classList.add('fa', 'fa-map-marker', 'fa-2x');
container.appendChild(icon);
var popup = new mapboxgl.Popup({
closeButton: false,
closeOnClick: false
})
.setLngLat([
datacenters[country][city].coordinates.lng,
datacenters[country][city].coordinates.lat
])
.setDOMContent(container)
.addTo(map);
});
CSS:
.map div.mapboxgl-popup-content {
background: none;
padding: 0;
}
.map .mapboxgl-popup-tip {
display: none;
}
I just hope someone comes up with a real solution, because this feels kinda dirty to me. But hey: it does the job just fine!
Mapbox Marker now has an element option see this link Mapbox Marker. So instead of appending the icon HTML to the Div element you can simply add into the options when creating a marker. I found this also gets rid of the offset problem. So using the code above you can do this....
var icon = document.createElement('i');
icon.classList.add('fas', 'fa-map-marker-alt');
icon.style.color = 'blue';
new mapboxgl.Marker(container, {anchor: 'center', offset: [0, 0], element: icon})
Also the CSS for the marker can be updated to allow a pointer
.mapboxgl-marker {
border: none;
cursor: pointer;
}
In many cases I need markercluster, it would be very interesting to show the rate of the total number of markers in the map (in form of %) instead of the number of the number of children of the cluster. So if you have 20 makers in that cluster and the total in the map is 200, I would like it to show 10% in that region of the map instead of 20.
Anyone has any idea of how to implement it?
thank you
Thank you for the solution, it is quite simple and works well, here is the code of the solution:
$.getJSON(link, function(data) {
var total_number = data.features.length; //Here I get the total number of markers
var others = L.geoJson(data, {
onEachFeature: function (feature, layer) {
layer.bindPopup('Number: ' + feature.properties.Nr+' Residency:'+ feature.properties.Lives);
},
})
var markers = L.markerClusterGroup({
iconCreateFunction: function(cluster) {
var markers = cluster.getAllChildMarkers();
var n = ((markers.length/total_number)*100).toFixed(1);;
return L.divIcon({ html: n+'%', className: 'mycluster', iconSize: L.point(40, 40) });
},
});
The CSS of the "myclusters" is the following:
.mycluster {
width: 150px;
height: 15px;
background-color: greenyellow;
text-align: center;
font-size: 18px;
}
Thanks you again
You can use this puglin:
https://github.com/Leaflet/Leaflet.markercluster
Example:
http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-realworld.388.html
http://leaflet.github.io/Leaflet.markercluster/example/marker-clustering-custom.html
To do a percentage, read this part:
https://github.com/Leaflet/Leaflet.markercluster#customising-the-clustered-markers
getChildCount() or getAllChildMarkers() provide all the marks
I am moving from leaflet+cloudmade to mapbox and have been doing minor rewrites to my code where necessary. I am refreshing my map and in my previous installment it was easiest to add each marker in to it's own layer and then on refresh to remove all layers and redraw the markers.
Here is my current code:
function setLeafletMarker(lat, lng, iconType, popupHTML) {
popupHTML = typeof popupHTML !== 'undefined' ? popupHTML : "";
var LamMarker = new L.Marker([lat, lng], { icon: iconType }); //.on('click', markerClick); ;
markers.push(LamMarker);
LamMarker.bindPopup(popupHTML);
map.addLayer(LamMarker);
}
I suspect this has something to do with the problem, which is that when I put my mouse cursor over a marker, it stays as a hand (draggable) instead of changing to be a pointy finger, meaning the marker is clickable. Clicking works fine, but it's not very intuitive. How do I change the hand to pointy finger?
Ran into the same problem also. Did a quick check of CSS on the mapbox site, and they seem to fix it using a css rule in their sitewide css file (not map specific). I was able to fix the problem using the same approach, by adding this to my sitewide css.
.leaflet-overlay-pane path,
.leaflet-marker-icon {
cursor: pointer;
}
I have compared the default leaflet.css with the default mapbox.css and leaflet includes this
.leaflet-clickable {
cursor: pointer;
}
while mapbox does not.
One way is you can just add the behavior to the mouseover and mouseout events:
LamMarker.on("mouseover", function(e) {
document.getElementById('map').style.cursor = "pointer";
}).on("mouseout", function(e) {
document.getElementById('map').style.cursor = "grab";
});
In the current mapbox api (2022) this works. I'm not sure if there is a smarter way to do this as the docs are terrible in this department.
map.on('mouseover', 'source-id', e => {
map.getCanvas().style.cursor = 'pointer'
})
map.on('mouseleave', 'source-id', e => {
map.getCanvas().style.cursor = ''
})
This assumes you are adding a source layer to your map as in this example
If your not using source layers, you can target your marker icon via css
.marker svg {
cursor: pointer;
}
I am trying to keep my basemap layer opacity at a constant between different selections (and can be controlled by the user with a slider). Any basemap layers that don't have a related 'reference' layer behave as expected (i.e. if topo is at 25% before changing to imagery, it will update to 25% on change). If a user selects a basemap that also has a reference layer (imagery with labels; light gray canvas, etc), the reference layer ignores the opacity setting when loaded and will only change AFTER the user tries to move the slider. Thoughts?
Fun tidbit... Basemap layer 'Terrain with Labels' ignores this completely on both the imagery and the text when swapping. It almost looks like it refreshes after it loads.
Here is the working example in JSFiddle (http://jsfiddle.net/disuse/ez6mN/) and the dojo code that I am using to replicate my issue. Using the latest Esri ArcGIS Javascript 3.7.
Codeblock
var baseMap_Opacity;
var baseOpacity = 0.25;
require([
"esri/map",
"esri/dijit/BasemapGallery",
"dijit/form/HorizontalSlider",
"dijit/form/HorizontalRule",
"dijit/form/HorizontalRuleLabels",
"dojo/parser",
"dojo/dom",
"dojo/on",
"dojo/ready",
"dojo/domReady!"
], function(
Map,
BasemapGallery,
HorizontalSlider,
HorizontalRule,
HorizontalRuleLabels,
parser,
dom,
on,
ready
) {
ready(function() {
map = new Map("map", {
center: [-121.569, 39.00],
zoom: 7,
optimizePanAnimation: true,
basemap: "topo"
});
var basemapGallery = new BasemapGallery({
showArcGISBasemaps: true,
map: map
}, "basemaps");
basemapGallery.startup();
basemap = map.getLayer("layer0");
basemap.setOpacity(baseOpacity);
on(basemapGallery, "selection-change", function() {
changeBasemapOpacity(baseOpacity);
});
createHorzSlider();
});
function createHorzSlider() {
baseMap_Opacity = dom.byId("baseMap_Opacity");
baseMap_Opacity.innerHTML = Math.round(baseOpacity*100) + "%";
var horzSlider = new HorizontalSlider({
minimum: 0,
maximum: 1,
value: baseOpacity,
intermediateChanges: true,
showButtons: true,
discreteValues: 101,
style: "width: 300px; margin-left: 25px;",
onChange: function(value) {
changeBasemapOpacity(value);
}
}, "horzSlider");
horzSlider.startup();
var horzSliderRule = new HorizontalRule({
container: "bottomDecoration",
count: 2 ,
style: "height: 5px; width: 288px; margin-top: 5px; margin-left: 32px;"
}, "horzSliderRule");
horzSliderRule.startup();
var horzSliderLabels = new HorizontalRuleLabels({
container: "bottomDecoration",
labels: ["0", "100"],
style: "width: 288px; margin-left: 32px;",
labelStyle: "font-style: normal; font-weight: bold; font-size: 14px;"
}, "horzSliderLabels");
horzSliderLabels.startup();
}
function changeBasemapOpacity(value) {
baseOpacity = value;
baseMap_Opacity.innerHTML = Math.round(baseOpacity*100) + "%";
var esriURL = "http://services.arcgisonline.com";
var layers = map.getLayersVisibleAtScale();
for (var i = 0; i < layers.length; i++) {
var lyr = map.getLayer(layers[i].id);
if ((lyr._basemapGalleryLayerType) || (lyr.id == "layer0") || ((lyr.url) && (lyr.url.indexOf(esriURL) == 0))) {
lyr.setOpacity(baseOpacity);
}
}
}
});
The basemap gallery's selection-change event fires after the newly selected basemap is in the map. This fires before reference layers are added and is the intended design, the idea being that you wouldn't want to manipulate reference layers. In your case, that's not what you want so using selection-change is out.
To accomplish what you want, use the map's layer-add-result event and check if layer._basemapGalleryLayerType is truthy. If it is, you know a layer used by the basemap gallery was added to the map and you should update its opacity. Here's a code snippet:
map.on("layer-add-result", function(e) {
if ( e.layer._basemapGalleryLayerType ) {
e.layer.setOpacity(baseOpacity);
}
});
Regarding the issue with the Terrain with Labels basemap, things are working as expected. Because that basemap's reference layer includes labels as well as political boundaries and major roads, it looks like opacity isn't being applied when in fact it is. Using the code above will set opacity on both the layer that represents the terrain basemap as well as the reference layer.
Here's a modified version of your page that I think accomplishes what you want: http://jsbin.com/IyixAPa/1/edit
So the background of my openlayers implementation appears to be squished into vertical stripes. The weird thing is that it wasn't always like this but even when I stash all my changes back to a point where I know it was working, it still is broken. It makes me wonder if perhaps something has changed about the way the tile assets are being delivered. I have tried switching between using osm and wms layers with no change, any help would be appreciated.
Here is the pertinent code:
initMap: function() {
var view = this;
var map = this.map = new OpenLayers.Map();
map.render(this.$map[0]);
var wmsLayer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
"http://vmap0.tiles.osgeo.org/wms/vmap0?", {layers: 'basic'});
var osmLayer = new OpenLayers.Layer.OSM();
this.layers = {
point: new OpenLayers.Layer.Vector("Point Layer"),
line: new OpenLayers.Layer.Vector("Line Layer"),
polygon: new OpenLayers.Layer.Vector("Polygon Layer")
};
this.setValue(this.value);
map.addLayers([this.layers.point, this.layers.line, this.layers.polygon, osmLayer]);
drawControls = {
point: new OpenLayers.Control.DrawFeature(this.layers.point,
OpenLayers.Handler.Point),
line: new OpenLayers.Control.DrawFeature(this.layers.line,
OpenLayers.Handler.Path),
polygon: new OpenLayers.Control.DrawFeature(this.layers.polygon,
OpenLayers.Handler.Polygon)
};
this.layers[this.layerType].events.on({'sketchcomplete': function(feature) {
if (!view.multiple) {
// deactivate polygon layer once a polygon has been added
drawControls[view.layerType].deactivate();
}
}});
for(var key in drawControls) {
map.addControl(drawControls[key]);
}
if (this.layers[this.layerType].features.length) {
var bounds = this.layers[this.layerType].getDataExtent();
var zoom = this.layers[this.layerType].getZoomForExtent(bounds);
var lon = (bounds.top - bounds.bottom) / 2;
var lat = (bounds.right - bounds.left) / 2;
map.setCenter(new OpenLayers.LonLat(lon,lat), 3);
map.zoomToExtent(bounds);
if (view.multiple) {
drawControls[view.layerType].activate();
}
} else {
map.setCenter(new OpenLayers.LonLat(-11174482.03751,4861394.9982606), 4);
drawControls[view.layerType].activate();
}
this.$('.clear').click(function(e) {
e.preventDefault();
view.layers[view.layerType].destroyFeatures();
drawControls[view.layerType].activate();
});
},
Here is the output:
So I found the problem. Twitter bootstrap has a line in its reset file that sets:
img { max-width: 100% }
This was squishing the images. You can fix it by doing:
img { max-width: none; }
I was having the same problem, are you using bootstrap of twitter?
I found out there was a selector for img elements that was affecting the map. I had the next selector into the bootstrap.css"
img {
max-width: 100%;
vertical-align: middle;
border: 0;
-ms-interpolation-mode: bicubic;
}
I don't know whay the parameter max-width: 100%; makes the vertical stripes in my case. I remove the propertie max-width: 100% and now is working.
Setting img { max-width: 100% } to img { max-width:none; } does resolve the problem.
However this will have an unintended effect on images throughout your website. I am also using Bootstrap Twitter's carousel component for images and when I made the above changed the images did not fit in the carousel. Therefore I changed it so that it only targets the images in the OpenLayers / OpenStreetMap div.
div#outlet_map img { max-width:none; }