How to check if a marker is in a cluster? - leaflet.markercluster

With Leaflet.Markercluster, how is it possible to check if a marker is in a cluster?

Use Leaflet's hasLayer() function
Regular visible markers technically exist as layers in the map. Leaflet also has a function hasLayer() that returns true/false whether a given reference, like a stored marker, currently exists (ie "is visible") in the map as a layer.
For my specific problem, I gave a special visual decoration to a selected map marker (e.g. red border), and this decoration remained even after that marker went in-and-out of a cluster. I needed the selected marker to deselect itself if it entered a cluster.
Below is rough logic that allows me to check whether a given marker reference is visible to the user after a cluster event (that may occur for any reason, like zoom events or non-user driven movements in the map).
Relevant abbreviated code:
I hold my clustering like this...
this.markersClustered = L.markerClusterGroup({ // Lots of stuff here... } })
In my click event for individual markers, I store the marker in a "selectedItem" object (which I use for all kinds of other logic in my app)...
onEachFeature(feature, marker) {
marker.on("click", e => {
// ... (lots of code) ...
// Save instance of the marker; I call it "layer" only
// because I only use this for the hasLayer() check
this.selectedItem.layer = marker
// Here do other stuff too, whatever you need...
//this.selectedItem.target = e.target
//this.selectedItem.latlng = e.latlng
//this.selectedItem.id = e.target.feature.id
//this.selectedItem.icon = layer._icon
// ... (lots of code) ...
})
}
When cluster animations end, using hasLayer(), check whether the selected marker is in a cluster, and do stuff.
Note: the check is done at the END of clustering animation in my use case, because I didn't want the user to see the selected marker lose it's special visual decoration while the clustering animation was happening (it would appear buggy).
this.markersClustered.on('animationend', (e) => {
// Check visibility of selected marker
if(this.map.hasLayer(this.selectedItem.layer)) {
// True: Not in a cluster (ie marker is visible on map)
} else {
// False: In a cluster (ie marker is not visible on map)
}
console.log("Selected marker visible?:" + this.map.hasLayer(this.selectedItem.layer))
})

Related

Wrong code in tutorial for event listeners

I am following this tutorial to build a store locator page with a Mapbox map.
I don't want to add custom markers because I already have custom map labels (symbols?), which means I don't need the optional last section of the tutorial and stop right after Add Event Listeners.
Once this is completed, the page should react to clicks in the side panel list, as well as on the map (2 event listeners). However, in the demo provided in the tutorial for that particular step, you can tell the code for the second event listener, the one making the map clickable, is not functioning, which makes me believe there is a mistake in the provided code:
// Add an event listener for when a user clicks on the map
map.on('click', function(e) {
// Query all the rendered points in the view
var features = map.queryRenderedFeatures(e.point, { layers: ['locations'] });
if (features.length) {
var clickedPoint = features[0];
// 1. Fly to the point
flyToStore(clickedPoint);
// 2. Close all other popups and display popup for clicked store
createPopUp(clickedPoint);
// 3. Highlight listing in sidebar (and remove highlight for all other listings)
var activeItem = document.getElementsByClassName('active');
if (activeItem[0]) {
activeItem[0].classList.remove('active');
}
// Find the index of the store.features that corresponds to the clickedPoint that fired the event listener
var selectedFeature = clickedPoint.properties.address;
for (var i = 0; i < stores.features.length; i++) {
if (stores.features[i].properties.address === selectedFeature) {
selectedFeatureIndex = i;
}
}
// Select the correct list item using the found index and add the active class
var listing = document.getElementById('listing-' + selectedFeatureIndex);
listing.classList.add('active');
}
});
Would anyone be able to tell what is wrong with this code?
Turns out the code is incomplete in that the cursor doesn't change to a pointer as you hover over a map label/marker so it doesn't clue you into realising you can click on it, hence my assumption it wasn't working at all. I assume the general users who would then face the map would be equally deceived unless the pointer shows up. So in the tutorial, if you do go ahead and click the marker, it will have the expected behaviour and display the popup, although no pointer is shown.
Here is how to create the pointer, based on this fiddle: https://jsfiddle.net/Twalsh88/5j70wm8n/25/
map.on('mouseenter', 'locations', function(e) {
// Change the cursor style as a UI indicator.
map.getCanvas().style.cursor = 'pointer';
});
map.on('mouseleave', 'locations', function() {
map.getCanvas().style.cursor = '';
});

(Maps) Avoid camera to comeback to prior position when I add a new Pin

I have an app that shows a Map and a Pin on the center of it(just like Uber and PedidosYa), I have a button that when I click it sends the location where the pin's on. And it makes to appear the closest stores around that pin.
My problem is that when the first time the map appears its centered in my location, I move the map around to locate the pin, and when I click the button I want the map to stay there, but its comeback to the prior location and THEN moves the camera to the location where I drop the pin. I want to avoid that moving.
The function I use when I click the button to drop the pin is something like this:
var CenterPos = customMap.GetMapCenterLocation();
var pinPersonal = new CustomPin()
{
Id = "000",
Position = new Position(CenterPos.Latitude, CenterPos.Longitude),
Label = "Mio",
Url = "Mío"
};
customMap.Pins.Add(pinPersonal);
This draws a pin where I click the button. If I keep it this way, it draws the pin, and the camera comesback to the prior location.
After I use something like this:
customMap.MoveToRegion(MapSpan.FromCenterAndRadius(
new Position(latitud, longitud), Distance.FromMiles(0.2)));
that makes the camera to move to the location I choose. But it comebacks always to the prior location and the moves to the new one.
Any idea? Im not sure from where this behavior comes.
Did you implement CustomMap according with Customizing a Map Pin - Xamarin?
Probably you should override OnMarkerClickListener.OnMarkerClick and return true in your custom renderer.
This means disable default behavior(centering map, open info-window) and you can implement your own behavior when pin clicked.
See this link.
Markers  |  Maps SDK for Android  |  Google Developers
/** Called when the user clicks a marker. */
#Override
public boolean onMarkerClick(final Marker marker) {
// Retrieve the data from the marker.
Integer clickCount = (Integer) marker.getTag();
// Check if a click count was set, then display the click count.
if (clickCount != null) {
clickCount = clickCount + 1;
marker.setTag(clickCount);
Toast.makeText(this,
marker.getTitle() +
" has been clicked " + clickCount + " times.",
Toast.LENGTH_SHORT).show();
}
// Return false to indicate that we have not consumed the event and that we wish
// for the default behavior to occur (which is for the camera to move such that the
// marker is centered and for the marker's info window to open, if it has one).
return false;
}

Leaflet 0.7.7 Zoombased layer switching separated by theme

We are working on a school project where the aim is to create a map in which based on the amount of zoom the layer switches from one aggregate level to a smaller aggregate level. Additionally, we have several groups of layers based on a theme for which this needs to apply. So you'd click on a theme and a new group of layers that switches based on zoom level become active and when you click another theme another group of layers become active and switch based on zoom level. This means that the themes are exclusionary, ideally you can't have more than one theme active at a time.
We tried to make this work in several ways already but without much success. Using the L.Control.Layers we were unable to group different layers together under one radio button and have them switch based on zoom since the layer control build into leaflet always splits them up into separate ones. Even using L.layerGroup to combine several layer variables or creating several layers into one variable and then adding them to the map using l.control.layer.
We also tried to use L.easyButton (https://github.com/CliffCloud/Leaflet.EasyButton). This allowed us to put the variables under one button and add a zoom based layer switching inside of it. However, the issue here is that we are unable to deactivate the functionality once activated. Which results in several of them being active at one point and overlapping each other.
If possible we would like to know if we should use a different approach or if either the leaflet control function or the use of easyButton could work and how?
This is example code for one of the buttons, which would appear several times but show a different theme:
L.easyButton( '<span class="star">&starf;</span>', function (polygon) {
var ejerlav_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:ejerlav',
transparent: true,
styles: 'polygon',
format: 'image/png'});
var municipality_polygon = new L.tileLayer.betterWms(
'http://[IP]:[PORT]/geoserver/prlayer/wms', {
layers: 'prlayer:municipality',
transparent: true,
styles: 'polygon',
format: 'image/png'});
map.on("zoomend", function() {
if (map.getZoom() <= 10 && map.getZoom() >= 2) {
map.addLayer(municipality_polygon);
} else if (map.getZoom() > 10 || map.getZoom() < 2) {
map.removeLayer(municipality_polygon);
}
});
map.on("zoomend", function() {
if (map.getZoom() <= 11 && map.getZoom() >= 11) {
map.addLayer(ejerlav_polygon);
} else if (map.getZoom() > 11 || map.getZoom() < 11) {
map.removeLayer(ejerlav_polygon);
}
});
}).addTo(map);
If my understanding is correct, you would like to give the user the ability to switch between "themes" (some sort of group of layers that switch themselves based on the map current zoom level), possibly using Leaflet Layers Control?
And regarding the switch based on map zoom, you cannot just change the Tile Layer template URL because you use some WMS?
As for the latter functionality (switching layers within a group / theme based on map zoom), a "simple" solution would be to create your own type of layer that will listen to map "zoomend" event and change the Tile Layer WMS accordingly.
L.LayerSwitchByZoom = L.Class.extend({
initialize: function (layersArray) {
var self = this;
this._layersByZoom = layersArray;
this._maxZoom = layersArray.length - 1;
this._switchByZoomReferenced = function () {
self._switchByZoom();
};
},
onAdd: function (map) {
this._map = map;
map.on("zoomend", this._switchByZoomReferenced);
this._switchByZoom();
},
onRemove: function (map) {
map.off("zoomend", this._switchByZoomReferenced);
this._removeCurrentLayer();
this._map = null;
},
addTo: function (map) {
map.addLayer(this);
return this;
},
_switchByZoom: function () {
var map = this._map,
z = Math.min(map.getZoom(), this._maxZoom);
this._removeCurrentLayer();
this._currentLayer = this._layersByZoom[z];
map.addLayer(this._currentLayer);
},
_removeCurrentLayer: function () {
if (this._currentLayer) {
map.removeLayer(this._currentLayer);
}
}
});
You would then instantiate that layer "theme" / group by specifying an array of layers (your Tile Layers WMS), where the array index corresponds to the zoom level at which that Tile Layer should appear.
var myLayerSwitchByZoomA = new L.LayerSwitchByZoom([
osmMapnik, // zoom 0, osmMapnik is a Tile Layer or any other layer
osmDE, // zoom 1
osmFR, // zoom 2
osmHOT // zoom 3, etc.
]);
Once this new layer type is set, you can use it in the Layers Control like any other type of Layer / Tile Layer, etc.
L.control.layers({
"OpenStreetMap": myLayerSwitchByZoomA,
"ThunderForest": myLayerSwitchByZoomB
}).addTo(map);
Demo: http://jsfiddle.net/ve2huzxw/85/
Note that you could further improve the implementation of L.LayerSwitchByZoom to avoid flickering when changing the layer after zoom end, etc.

Change Drupal Gmap marker z-index

I have a Drupal ExtendedGmap View. View results show as markers on the map. Marker type is determined by a custom field (NOT a field on the content type, but rather a PHP field calculated from the View) so the first view result marker is set to 'orange' and all other row markers are set to 'green'. The problem I have is that I want my first (orange) marker to show above the others. I have found a way to change the first marker z-index value in THEME_preprocess_gmap_views_view_gmapextended function:
$vars['markers'][0]['opts']['zindex'] = '9999';
But this is not reflected on the map and the first marker is still buried (in fact the first marker ends up somewhere in the middle of the stack).
How do I get my first View row marker on top?
I tried the Javascript mentioned on this page but don't really understand it and it doesn't work for me.
Drupal.gmap.addHandler('gmap',
function (elem)
{
var obj = this;
obj.bind('preparemarker',
function (marker)
{
marker.opts.zIndexProcess =
function (marker,b)
{
return this.zindex ? this.zindex : -99999;
};
}
);
});
I am using Drupal 7 and Gmap 7.x-2.9
Found the problem. Googlemaps API V3 uses zIndex (capital 'I') instead of zindex. Changed that and works as expected - markers stack correctly.
Example code:
$vars['markers'][0]['opts']['zIndex'] = '9999';

HTML5 Drag and Drop - Map

I have to make a map of Europe with its countries
and then I need a few pics of products from those countries
After that I have to match the pic with the country with drag and drop
if the product is dropped on the correct country it should send me to another page (with more info about the product)
if it's wrong it should display a message
anyone have an idea? I checked for some basic drag and drop stuff but since I'm new to html5 etc and webdesign in general it's really hard to make this from scratch
thanks!
EDIT: also only use HTML, CSS, JS
This can be achieved with the MapQuest JavaScript API. What I would start with is by adding polygon overlays to the map for each country, the colour can be sett to completely transparent by setting the opacity for the overlay to 0.0. From each overlay add a mouseup event listener to each overlay, this event listener can then be used to determine what it was you were dragging in the first place.
For the drag start functionality you can either do this yourself or you could use something like the jQuery UI draggable support, you could then use the dragstop event from the draggable API in conjunction with mouseup on the overlay to perform your logic.
Check out the basic map to get a map going.
Some code to start with
var countryCode;
// Adds an overlay and wires an event for mouseup.
function addMapOverlay(points, cc) {
var poly = new MQA.PolygonOverlay();
poly.setShapePoints(points);
poly.color = "#ffffff";
poly.colorAlpha=0.0;
poly.fillColor = "#ffffff";
poly.fillColorAlpha=0.0;
poly.addListener(rectangle, 'mouseup', function(evt) {
if (evt.eventName === "mouseup") {
// Here you have the event firing for the mouse-up on the overlay.
countryCode = cc;
}
});
}
For the drag-start.
$("#some-country-item").draggable({
start: function(event, ui) {
countryCode = null;
},
stop: function(event, ui) {
if (countryCode === "what you expected") {
// Released on correct country.
} else {
// Did not release on correct country.
}
}
});
You may need to test the event handling to ensure that the correct events are fired in the right order, or use the mouseover event on the overlay object.
The code samples are theoretical and should help you find the right direction to go.