i am JS / jQuery beginner and maybe is stupid but i have problem with add + remove custom buttons to Leaflet LayersControl on mobile devices.
Description of project:
I have map with multiple layers and i need to add two buttos for check / uncheck all checkboxes (it works in example).
Here is my example and is neccessary to turn on touch simulation in browser:
https://jsfiddle.net/lukassliacky/n2ep6yg0/42/ (created and tested on Firefox).
As i said, i am JS still beginner and for me seems to be the most easiest solution adding custom HTML with after but i have problem with events on touch devices.
On desktop (non touch) it looks quite simple: in top right corner is icon for collapsed LayerControl and i am using events mouseenter and mouseleave for add and remove custom buttons. It looks fine and i think that is good and working solution (this case isn't included in my example)
And i have problem on mobile / touch devices because i don't know which events can i use for add + remove.
I think that for add event looks good e.g. click, tap or touchstart but i don't know how to remove buttons when LayerControl is expanded and it will be collapsed.
Its quiet complicated for me because i need to exlude some divs from map (LayerControl with checboxes - when i would to click on checboxes, my custom buttons will be removed)
$('#main-map:not(leaflet-control-layers-expanded > div).click(function() {
$('.leaflet-custom-buttons').remove()
})
$('.leaflet-control').click(function(e) {
e.stopPropagation();
});
When i have expanded LayersControl (with buttons) and when i click on Pin marker, buttons are removed and LayerControl is still expanded (it isn't good). In this case is LayersControl collaped after click on different object as Pin Marker.
Please, can you help me with using right events for add / remove custom buttons on mobile devices? Of course, maybe is my approach wrong and it will be necessary to rewrite my code.
Thank you
Okey, i tried different approach and seems to be a correct way.
Updated example with working custom buttons is here: https://jsfiddle.net/lukassliacky/n2ep6yg0/57/
var cities = L.layerGroup();
var secondLayer = L.layerGroup();
var thirdLayer = L.layerGroup();
L.marker([39.61, -105.02]).bindPopup('This is Littleton, CO.').addTo(cities),
L.marker([39.24, -104.39]).bindPopup('This is Denver, CO.').addTo(cities),
L.marker([39.33, -103.2]).bindPopup('This is Aurora, CO.').addTo(cities),
L.marker([39.17, -105.43]).bindPopup('This is Golden, CO.').addTo(cities);
L.marker([39.51, -105.52]).bindPopup('This is Littleton, CO.').addTo(secondLayer),
L.marker([39.34, -104.29]).bindPopup('This is Denver, CO.').addTo(secondLayer),
L.marker([39.13, -104.3]).bindPopup('This is Aurora, CO.').addTo(secondLayer),
L.marker([39.97, -105.63]).bindPopup('This is Golden, CO.').addTo(secondLayer);
L.marker([39.21, -105.52]).bindPopup('This is Littleton, CO.').addTo(thirdLayer),
L.marker([39.34, -104.09]).bindPopup('This is Denver, CO.').addTo(thirdLayer),
L.marker([39.73, -104.28]).bindPopup('This is Aurora, CO.').addTo(thirdLayer),
L.marker([39.17, -105.53]).bindPopup('This is Golden, CO.').addTo(thirdLayer);
var mbAttr = 'Map data © OpenStreetMap contributors, ' +
'Imagery © Mapbox',
mbUrl = 'https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw';
var grayscale = L.tileLayer(mbUrl, {id: 'mapbox/light-v9', tileSize: 512, zoomOffset: -1, attribution: mbAttr}),
streets = L.tileLayer(mbUrl, {id: 'mapbox/streets-v11', tileSize: 512, zoomOffset: -1, attribution: mbAttr});
var map = L.map('main-map', {
center: [39.73, -104.99],
zoom: 7,
layers: [grayscale, cities, secondLayer, thirdLayer]
});
var baseLayers = {
/* "Grayscale": grayscale,
"Streets": streets */
};
var overlays = {
"Cities": cities,
"SecondLayer": secondLayer,
"ThirdLayer": thirdLayer,
};
L.Control.Custom = L.Control.Layers.extend({
onAdd: function () {
this._initLayout();
this._addCheckButton();
this._addClearButton();
this._update();
return this._container;
},
_addCheckButton: function () {
var elements = this._container.getElementsByClassName('leaflet-control-layers-list');
var button = L.DomUtil.create('button', 'btn btn-primary rounded-0 my-1 me-1 btn-xs leaflet-btn-check-all', elements[0]);
button.textContent = 'Check All';
L.DomEvent.on(button, 'click', function(e){
L.DomEvent.stop(e);
// fire this function (check all)
$('.leaflet-control input[type="checkbox"]').trigger('click').prop('checked', true);
}, this);
},
_addClearButton: function () {
var elements = this._container.getElementsByClassName('leaflet-control-layers-list');
var button = L.DomUtil.create('button', 'btn btn-primary rounded-0 my-1 btn-xs w-50 leaflet-btn-check-all', elements[0]);
button.textContent = 'Clear All';
L.DomEvent.on(button, 'click', function(e){
L.DomEvent.stop(e);
// fire this function (uncheck all)
$('.leaflet-control input[type="checkbox"]').trigger('click').prop('checked', false);
}, this);
}
});
new L.Control.Custom(
baseLayers,
overlays,
{collapsed:true}).addTo(map);
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<link href="https://cdn.jsdelivr.net/npm/bootstrap#5.1.1/dist/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdn.jsdelivr.net/npm/bootstrap#5.1.1/dist/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/leaflet-locatecontrol/0.74.0/L.Control.Locate.min.js"></script>
<link href="https://unpkg.com/leaflet#1.7.1/dist/leaflet.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet#1.7.1/dist/leaflet.js"></script>
<style>
html, body {
height: 100%;
margin: 0;
}
#main-map {
width: 100%;
height: 500px;
}
</style>
</head>
<body>
<div id='main-map'></div>
My solution is based on https://embed.plnkr.co/Je7c0m/ and i am really thank to author.
Grzegorz T. thank you for your support too.
Related
Relatively new JavaScript user here, first question.
So I have a choropleth leaflet map that uses a jQuery slider (via https://github.com/dwilhelm89/LeafletSlider) to shift between years. The map contains about 50 years of global data, with each overlay layer corresponding to a layergroup containing each country's data for the appropriate year.
The purpose of the slider is to allow the user to quickly shift between years. However, I would like a visual cue to let the user know what year is being displayed at any moment. Is it possible to display something like a text box on the map that displays the name of the current overlay layer and automatically updates whenever the overlay layer switches? (the name of each layergroup is its respective year)
I know the textbox part is certainly possible
(Overlaying a text box on a leaflet.js map), but I'm not sure how to dynamically update it with the necessary info.
Thanks! Let me know if you need my code and I'll post it.
Okay, I thought a bit and here's a quick solution.
var sliderControl = null;
var map = L.map("map").setView([51.95, 7.6], 9);
L.tileLayer("//{s}.tile.osm.org/{z}/{x}/{y}.png", {
attribution:
'© OpenStreetMap contributors',
}).addTo(map);
//Fetch some data from a GeoJSON file
$.getJSON(
"https://dwilhelm89.github.io/LeafletSlider/points.json",
function (json) {
var testlayer = L.geoJson(json);
var sliderControl = L.control.sliderControl({
position: "topright",
layer: testlayer,
range: true,
});
//Make sure to add the slider to the map ;-)
map.addControl(sliderControl);
//An initialize the slider
sliderControl.startSlider();
}
);
map.on("layeradd", function () {
map.eachLayer(function (layer) {
if (layer instanceof L.Marker) {
let desc = document.querySelector(".description");
// desc.textContent = JSON.stringify(layer.getLatLng());
desc.textContent = layer.feature.properties.time;
}
});
});
// create legend
const legend = L.control({ position: "bottomleft" });
legend.onAdd = function () {
let div = L.DomUtil.create("div", "description");
div.className = "description";
return div;
};
legend.addTo(map);
*,
:after,
:before {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
height: 100%;
}
body,
html,
#map {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
}
.description {
border: 1px solid black;
background: #fff;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" />
<link rel="stylesheet" href="https://code.jquery.com/ui/1.9.2/themes/base/jquery-ui.css" type="text/css">
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"></script>
<script src="https://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="https://code.jquery.com/ui/1.9.2/jquery-ui.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui-touch-punch/0.2.2/jquery.ui.touch-punch.min.js"></script>
<script src="https://rawgit.com/dwilhelm89/LeafletSlider/master/SliderControl.js" type="text/javascript"></script>
<div id="map"></div>
And I recommend using the newer version of this plugin ;)
There you have the method event
And the easier way to download the data about the marker.
I've used Leaflet, Mapbox, and Google Maps on some personal and commercial projects, and whenever I wanted to overlay some information, I'd just use simple HTML elements. All you need to do is render whatever elements you want on the screen the same way you would normally, just ensure that you use correct position and applicable positioning units and ensure you have a higher z-index on your element that you want to show, i.e. your year indicator, than you do on your map element. Treat it just like you would any other HTML!
Edit:
Here is an example screenshot: https://imgur.com/a/2fXf5CI. Also, if you aren't already using a position property on your Leaflet map, you should go ahead and add a position: relative; property to the selector for the map so that you can also assign it a z-index. And then, in your year indicator's styles, give it a higher z-index value than the one you gave to your Leaflet map.
Currently trying to implement/solve an issue with an ESRI ArcGIS map environment where I have a multi-layered map with custom graphics being rendered in each layer. Some of the graphics are simple shapes such as lines and circles, but a majority of the graphics are icons (.png) files that are being drawn on the layers. (All of this is being done in JavaScript.)
I have been able to get all of the layers generated correctly - the data IS NOT being stored in ArcGIS maps but a custom designed Contact & Location database (SQL) and other forms within the web application maintain this C&L data.
The graphic icons that are rendered on the map need to have a mouse-over tooltip popup appear with information that has been stored with the icon when it is created where the .substitute() command will update the template. The information displayed is HTML formatted in a <div>.
Problem:
When the mouse is moved over an icon, the tooltipDialog appears but 1) it always appears in the lower right corner of the screen - despite "orient:" and specific "x:" and "y:" coordinates being specified. Additionally when the tooltipDialog.open() command is executed, the dialog's offsetHeight is set to 624 and the offsetTop is set to 502. (The offsetWidth is actually set to the correct value.) How do I override either/both the offsetHeight/offsetTop?
I have tried specifying additional parameters to the tooltipDialog.open() command but nothing tried so far has altered the outcome. Even when I change the template content to be as simple as "Hi There!" does not change the outcome.
Note: If I click on an icon the IconWindow dialog will popup with the proper content and formatting being displayed. So it leads me to believe that the issue is within CSS or some other aspect of dojo/dijit as the tooltipDialog.open() command is actually where the offset changes are being made - the values are retained (offsetTop=0 offsetHeight=0) prior to the open() call.
Ideas/Recommendations?
You could try to use the dijit/popup module to open the TooltipDialog, which would allow you to pass in the DOM node around which the tooltip should be opened:
popup.open({
popup: myTooltipDialog,
around: dom.byId('thenode')
});
There a full example of this here (next to "A TooltipDialog may be popped up from any node.")
Well, It seems like you want to show info popup with some offset value whenever you hover on any feature on the map.
Solution-
Well to do so i don't think you need to deal with TooltipDialog because whenever you are loading feature or feature layer on the map you can attach info popup to it. It will take care of entire loading and displaying info popup Dialog along with its positioning.
To pass offset value-
If want to pass some offset value to popup dialog you can use below mentioned properties:-
For more Properties of popup dialog refer this link- https://developers.arcgis.com/javascript/3/jsapi/popup.html
Hovering Dialog sample-
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<meta name="viewport" content="initial-scale=1, maximum-scale=1,user-scalable=no">
<title>Feature Layer - display results as an InfoWindow onHover</title>
<link rel="stylesheet" href="https://js.arcgis.com/3.18/dijit/themes/tundra/tundra.css">
<link rel="stylesheet" href="https://js.arcgis.com/3.18/esri/css/esri.css">
<style>
html, body, #mapDiv {
padding:0;
margin:0;
height:100%;
}
#mapDiv {
position: relative;
}
#info {
background: #fff;
box-shadow: 0 0 5px #888;
left: 1em;
padding: 0.5em;
position: absolute;
top: 1em;
z-index: 40;
}
</style>
<script src="https://js.arcgis.com/3.18/"></script>
<script>
var map, dialog;
require([
"esri/map", "esri/layers/FeatureLayer",
"esri/symbols/SimpleFillSymbol", "esri/symbols/SimpleLineSymbol",
"esri/renderers/SimpleRenderer", "esri/graphic", "esri/lang",
"esri/Color", "dojo/number", "dojo/dom-style",
"dijit/TooltipDialog", "dijit/popup", "dojo/domReady!"
], function(
Map, FeatureLayer,
SimpleFillSymbol, SimpleLineSymbol,
SimpleRenderer, Graphic, esriLang,
Color, number, domStyle,
TooltipDialog, dijitPopup
) {
map = new Map("mapDiv", {
basemap: "streets",
center: [-80.94, 33.646],
zoom: 8,
slider: false
});
var southCarolinaCounties = new FeatureLayer("https://sampleserver1.arcgisonline.com/ArcGIS/rest/services/Demographics/ESRI_Census_USA/MapServer/3", {
mode: FeatureLayer.MODE_SNAPSHOT,
outFields: ["NAME", "POP2000", "POP2007", "POP00_SQMI", "POP07_SQMI"]
});
southCarolinaCounties.setDefinitionExpression("STATE_NAME = 'South Carolina'");
var symbol = new SimpleFillSymbol(
SimpleFillSymbol.STYLE_SOLID,
new SimpleLineSymbol(
SimpleLineSymbol.STYLE_SOLID,
new Color([255,255,255,0.35]),
1
),
new Color([125,125,125,0.35])
);
southCarolinaCounties.setRenderer(new SimpleRenderer(symbol));
map.addLayer(southCarolinaCounties);
map.infoWindow.resize(245,125);
dialog = new TooltipDialog({
id: "tooltipDialog",
style: "position: absolute; width: 250px; font: normal normal normal 10pt Helvetica;z-index:100"
});
dialog.startup();
var highlightSymbol = new SimpleFillSymbol(
SimpleFillSymbol.STYLE_SOLID,
new SimpleLineSymbol(
SimpleLineSymbol.STYLE_SOLID,
new Color([255,0,0]), 3
),
new Color([125,125,125,0.35])
);
//close the dialog when the mouse leaves the highlight graphic
map.on("load", function(){
map.graphics.enableMouseEvents();
map.graphics.on("mouse-out", closeDialog);
});
//listen for when the onMouseOver event fires on the countiesGraphicsLayer
//when fired, create a new graphic with the geometry from the event.graphic and add it to the maps graphics layer
southCarolinaCounties.on("mouse-over", function(evt){
var t = "<b>${NAME}</b><hr><b>2000 Population: </b>${POP2000:NumberFormat}<br>"
+ "<b>2000 Population per Sq. Mi.: </b>${POP00_SQMI:NumberFormat}<br>"
+ "<b>2007 Population: </b>${POP2007:NumberFormat}<br>"
+ "<b>2007 Population per Sq. Mi.: </b>${POP07_SQMI:NumberFormat}";
var content = esriLang.substitute(evt.graphic.attributes,t);
var highlightGraphic = new Graphic(evt.graphic.geometry,highlightSymbol);
map.graphics.add(highlightGraphic);
dialog.setContent(content);
domStyle.set(dialog.domNode, "opacity", 0.85);
dijitPopup.open({
popup: dialog,
x: evt.pageX,
y: evt.pageY
});
});
function closeDialog() {
map.graphics.clear();
dijitPopup.close(dialog);
}
});
</script>
</head>
<body class="tundra">
<div id="mapDiv">
<div id="info">
Hover over a county in South Carolina to get more information.
</div>
</div>
</body>
</html>
Hoping this will help you :)
However its always recommended to add your code here if you are looking for exact Fix.
Found the answer to my situation. There is what appears to be an unstated requirement that either one of the supplied CSS dijit/themes be utilized OR the user must create their own theme which has some CSS that configures the display location.
The offsetTop issue was resolved by using eliminating any style references to top:.
Im Trying to implement a heatmap to Leaflet via the Leafletplugin//www.patrick-wied.at/static/heatmapjs/plugin-leaflet-layer.html,
but for some reason it seams to ignore my "Value" so all datapoints have the same colour
window.onload = function() {
var baseLayer = L.tileLayer(
'http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png',{
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade',
maxZoom: 20
}
);
var cfg = {
// radius should be small ONLY if scaleRadius is true (or small radius is intended)
"radius": 0.00007,
minOpacity: 0.5,
maxOpacity: 1,
// scales the radius based on map zoom
"scaleRadius": true,
// if set to false the heatmap uses the global maximum for colorization
// if activated: uses the data maximum within the current map boundaries
// (there will always be a red spot with useLocalExtremas true)
"useLocalExtrema": false,
// which field name in your data represents the latitude - default "lat"
latField: 'lat',
// which field name in your data represents the longitude - default "lng"
lngField: 'lng',
// which field name in your data represents the data value - default "value"
value: 'sig',
blur:0,
gradient: {
// enter n keys between 0 and 1 here
// for gradient color customization
'1': 'red',
'.3': 'yellow',
'0.9': 'green'
},
};
var heatmapLayer = new HeatmapOverlay(cfg);
var map = new L.Map('map-canvas', {
center: new L.LatLng(52.400458, 13.052260),
zoom: 14,
layers: [baseLayer, heatmapLayer]
});
heatmapLayer.setData(testData);
// make accessible for debugging
layer = heatmapLayer;
};
my data looks like this:
var testData = {
data:[{lat:52.40486, lng:13.04916, sig:30}, {lat:52.40486, lng:13.04916, sig:70}, {lat:52.40496, lng:13.04894, sig:67}, {lat:52.40496, lng:13.04894, sig:72}, {lat:52.40486, lng:13.04916, sig:74}, {lat:52.40486, lng:13.04916, sig:78}, {lat:52.40493, lng:13.04925, sig:67},]}
you can se it live on http://www.frief.de/heatmap/test2.html
would be great if someone has an idea, mybe Im just to stupid
Just a quick suggestion.
Have you tried using the Leaflet.Heatmap plug-in by Vladimir Agafonkin(the author of Leaflet.js himself). It seems it's not listed on the plug-ins page.
I think it's faster and probably will be a better solution: https://github.com/Leaflet/Leaflet.heat
http://mourner.github.io/simpleheat/demo/
I think this is not working because your code is wrong around here:
<div class="wrapper">
<div class="heatmap" id="map-canvas">
</div>
</div>
</script> <----THIS <script src="src/heatmap.js"></script>
<script src="src/leaflet-heatmap.js"></script>
Open link you said is a demo page and inspect code. Fix this orphaned </script tag and see if it's working now.
I know this is old, but I just ran into this issue, so here's how I solved it.
In the library "pa7_leaflet_hm.min.js" there's a part where it sets the min/max values, and it's hardcoded to 1 and 0
this._data = [];
this._max = 1;
this._min = 0;
Apparently this controls the intensity of the spots based on the value, and this is only overwritten if useLocalExtrema is set to false, which would always set it to the highest visible spot.
If you don't wish to always check the highest value based on the visible area, you can just change the this._max value to something higher, or maybe even set it to a value from the config, to expose it
this._data = [];
this._max = (this.cfg.max ? this.cfg.max : 1);
this._min = (this.cfg.min ? this.cfg.min : 0);
this way you would get a more traditional functioning heatmap
I am trying to change the default dropdown menu icon in the layer control. I'd like to have text alongside the icon. Is there any way to do this? Perhaps using JQuery and CSS?
I'm working on a leaflet project based on this example: http://leafletjs.com/examples/layers-control.html
Code:
<html>
<head>
<title>Leaflet Layers Control Example</title>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="../dist/leaflet.css" />
</head>
<body>
<div id="map" style="width: 600px; height: 400px"></div>
<script src="../dist/leaflet.js"></script>
<script>
var cities = new L.LayerGroup();
L.marker([39.61, -105.02]).bindPopup('This is Littleton, CO.').addTo(cities),
L.marker([39.74, -104.99]).bindPopup('This is Denver, CO.').addTo(cities),
L.marker([39.73, -104.8]).bindPopup('This is Aurora, CO.').addTo(cities),
L.marker([39.77, -105.23]).bindPopup('This is Golden, CO.').addTo(cities);
var cmAttr = 'Map data © 2011 OpenStreetMap contributors, Imagery © 2011 Clou dMade',
cmUrl = 'http://{s}.tile.cloudmade.com/BC9A493B41014CAABB98F0471D759707/{styleId}/256/{z}/{x}/{y}.png';
var minimal = L.tileLayer(cmUrl, {styleId: 22677, attribution: cmAttr}),
midnight = L.tileLayer(cmUrl, {styleId: 999, attribution: cmAttr}),
motorways = L.tileLayer(cmUrl, {styleId: 46561, attribution: cmAttr});
var map = L.map('map', {
center: [39.73, -104.99],
zoom: 10,
layers: [minimal, motorways, cities]
});
var baseLayers = {
"Minimal": minimal,
"Night View": midnight
};
var overlays = {
"Motorways": motorways,
"Cities": cities
};
L.control.layers(baseLayers, overlays).addTo(map);
</script>
Add this style after leaflet.css:
<style>
.leaflet-control-layers-toggle:after{
content:"your text";
color:#000 ;
}
.leaflet-control-layers-toggle{
width:auto;
background-position:3px 50% ;
padding:3px;
padding-left:36px;
text-decoration:none;
line-height:36px;
}
</style>
You can use HTML and you can change text when you define baselayers dans overlays.
Example:
var baseLayers = {
"<div id='my-div-id'><img src='img/icon-minimal.png' />Minimal View</div>": minimal,
"<div id='my-div-id'><img src='img/icon-night.png' />Super Night View</div>": midnight
};
var overlays = {
"<img src='myimage.png' /> Motorways": motorways,
"<img src='myimage2.png' /> All Cities": cities
};
If you are using Jquery you can add this to the document ready section
$(document).ready(function () {
$('.leaflet-control-layers').css({ 'width': 'auto', 'float': 'left' });
$('.leaflet-control-layers-toggle').css('float', 'left');
$('.leaflet-control-layers-toggle').after('<div class='control-extend'>Your text goes here</div>')
$('.control-extend').css({ 'float': 'left', 'line-height': '35px', 'font-weight': 'bold', 'margin-right' : '10px'});
$('.leaflet-control-layers').on('mouseover', function (e) {
$('.control-extend').hide();
});
$('.leaflet-control-layers').on('mouseout', function (e) {
$('.control-extend').show();
});
});
This actually doesn't work because there is apparently no way to control the vertical spacing between the radio buttons, which means that the buttons don't line up with the labels (and images).
The only example of this sort of thing I have seen that ACTUALLY WORKS is in the Leaflet Providers plug-in demo page in the package at https://github.com/leaflet-extras/leaflet-providers. What he apparently has done is to place the radio button and label on a overlay above the actual layer control, sort of like the Leaflet attribution overlay at the bottom right of most Leaflet maps...
This is what I did:
var overlayMaps = {
" <img src=\'scripts/images/eagle-icon.png\' height=\'50px\' /><br/>City Foundation Dates<br/> ": lg_cities,
" <img src=\'scripts/images/milestone.png\' height=\'50px\' /> <br/>The Golden Milestone": lg_specialMarkers
}
I tweaked my control layer with a small image...
If you want to make the check boxes prettier with low effort just add materialize icons and materialize css to your code.
This will make your layer control fancier:
This will also add a nice effect to checkboxes.
And you could also change the color of the texts, if icons make it look too busy:
this.layerControl.addOverlay(
nature,
'<span style="color: #31882A; ">Nature</span>'
);
I am looking for a Google Map V3 context menu library. I have found some code examples here
Gizzmo's blog
Google API tips
GMap3
How I got ..
Stack overflow question Google maps v3 - Contextual menu available? of April also just came up with the above examples. So did Gmap3 adding a simple context menu .
But maybe somebody has encapsulated the examples in a reusable library or found something in the meantime. Obviously there was something for V2.
-- Updated 2012-05-31 --
I have found another one http://googlemapsmania.blogspot.de/2012/04/create-google-maps-context-menu.html , but did not have the time to test it yet.
I don't think you need a library for this. I'd start by trying:
var contextMenu = google.maps.event.addListener(
map,
"rightclick",
function( event ) {
// use JS Dom methods to create the menu
// use event.pixel.x and event.pixel.y
// to position menu at mouse position
console.log( event );
}
);
This assumes your map was created with:
var map = new google.maps.map( { [map options] } );
The event object inside the callback has 4 properties
latLng
ma
pixel
where pixel.x and pixel.y are the offset where your click event triggered - counted from the upper left corner of the canvas holding the map object.
I have created a working JS Fiddle for showing context menu as well as the ability to have clickable items on this context menu.
It shows a clickable Context Menu when a marker is right clicked on Google map.
Basically it makes use of an OverlayView on map. BTW its just a demo.
var loc, map, marker, contextMenu;
ContextMenu.prototype = new google.maps.OverlayView();
/**
* onAdd is called when the map's panes are ready and the overlay has been
* added to the map.
*/
ContextMenu.prototype.onAdd = function() {
$("<div id='cMenu' class='context-menu-marker'></div>").appendTo(document.body);
var divOuter = $("#cMenu").get(0);
for(var i=0;i < this.menuItems.length;i++) {
var mItem = this.menuItems[i];
$('<div id="' + mItem.id + '" class="options-marker">' +
mItem.label + '</div>').appendTo(divOuter);
}
this.div_ = divOuter;
// Add the element to the "overlayLayer" pane.
var panes = this.getPanes();
//panes.overlayLayer.appendChild();
panes.overlayMouseTarget.appendChild(this.div_);
var me = this;
for(var i=0;i < this.menuItems.length;i++) {
var mItem = this.menuItems[i];
var func = function() {
me.clickedItem = this.id;
google.maps.event.trigger(me, 'click');
};
google.maps.event.addDomListener($("#" + mItem.id).get(0), 'click', $.proxy(func, mItem));
}
google.maps.event.addListener(me, 'click', function() {
alert(me.clickedItem);
});
};
ContextMenu.prototype.draw = function() {
var div = this.div_;
div.style.left = '0px';
div.style.top = '0px';
div.style.width = '100px';
div.style.height = '50px';
};
// The onRemove() method will be called automatically from the API if
// we ever set the overlay's map property to 'null'.
ContextMenu.prototype.onRemove = function() {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
};
// Set the visibility to 'hidden' or 'visible'.
ContextMenu.prototype.hide = function() {
if (this.div_) {
// The visibility property must be a string enclosed in quotes.
this.div_.style.visibility = 'hidden';
}
};
ContextMenu.prototype.show = function(cpx) {
if (this.div_) {
var div = this.div_;
div.style.left = cpx.x + 'px';
div.style.top = cpx.y + 'px';
this.div_.style.visibility = 'visible';
}
};
function ContextMenu(map,options) {
options = options || {}; //in case no options are passed to the constructor
this.setMap(map); //tells the overlay which map it needs to draw on
this.mapDiv = map.getDiv(); //Div container that the map exists in
this.menuItems = options.menuItems || {}; //specific to context menus
this.isVisible = false; //used to hide or show the context menu
}
function initialize() {
loc = new google.maps.LatLng(62.323907, -150.109291);
var options = {};
var menuItems=[];
menuItems.push({id:"zoomIn", className:'context_menu_item', eventName:'zoom_in_click', label:'Zoom in'});
menuItems.push({id:"zoomOut", className:'context_menu_item', eventName:'zoom_out_click', label:'Zoom out'});
options.menuItems = menuItems;
//=========================================
map = new google.maps.Map(document.getElementById("map"), {
zoom: 12,
center: loc,
mapTypeId: google.maps.MapTypeId.ROADMAP
});
marker = new google.maps.Marker({
map: map,
position: loc,
visible: true
});
contextMenu = new ContextMenu(map, options);
google.maps.event.addListener(marker, 'rightclick', function(mouseEvent){
contextMenu.hide();
this.clickedMarker_ = this;
var overlayProjection = contextMenu.getProjection();
var cpx = overlayProjection.fromLatLngToContainerPixel(mouseEvent.latLng);
contextMenu.show(cpx);
map.setOptions({ draggableCursor: 'pointer' });
});
// Hide context menu on several events
google.maps.event.addListener(map,'click', function(){
map.setOptions({ draggableCursor: 'grab' });
contextMenu.hide();
});
}
google.maps.event.addDomListener(window, 'load', initialize);
Fiddle link:
http://jsfiddle.net/jEhJ3/3409/
You can add context menu very easily in google map by following these steps:
Add a custom control of google maps, hide that control on page load.
Add a right click event handler on map.
Show that custom control on right click at correct position using pixel property of right click event parameter.
Moreover, Following is working snippet, open it in full page (use you own key to avoid that google billing error):
var map;
var karachi = {
lat: 24.8567575,
lng: 66.9701725
};
$(document).ready(function() {
initMap();
});
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 13.5,
center: karachi
});
let contextMenu = document.getElementById('contextMenu');
map.controls[google.maps.ControlPosition.TOP_CENTER].push(contextMenu);
hideContextMenu();
google.maps.event.addListener(map, "rightclick", function(event) {
showContextMenu(event);
});
google.maps.event.addListener(map, "click", function(event) {
hideContextMenu();
});
}
function showContextMenu(event) {
$('#contextMenu').css("display", "block");
$('#contextMenu').css({
left: event.pixel.x,
top: event.pixel.y
})
}
function hideContextMenu() {
$('#contextMenu').css("display", "none");
}
#map {
height: 100%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
.contextMenu {
background-color: rgb(255, 255, 255);
border: 2px solid rgb(255, 255, 255);
border-radius: 3px;
box-shadow: rgba(0, 0, 0, 0.3) 0px 2px 6px;
cursor: pointer;
font-size: 1rem;
text-align: center;
color: #0d1f49;
width: 20vw;
margin: 1px;/*Please note that this margin is necessary otherwise browser will open its own context menu*/
}
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAGlM3LLIL2j4Wm-WQ9qUz7I7ZpBsUx1X8">
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="map"></div>
<div id="contextMenu" class="contextMenu">
<div onclick="alert('On click of item 1 is called')">
Item 1
</div>
</div>
Go to this demo-purpose website: http://easysublease.org/mapcoverjs/
For context menu, I do not suggest implementing one subclass of the overlayView Class provided by Google Maps API. First, one instance of subclass of overlayView should be added to the five panes provided by Google. More possibly one should add this instance to pane overlayMouseTarget .
But, this instance is "shadowed" by other dom over it. So normal original browser event such mouseover, mouseout cannot reach this instance.
One must use Google Maps API method: addDomListener to handle it(why?). It requires lots of JavaScript code to implement different event handlers, do lots of css class adding and deleting just to realize some visual effects, which could be done using several lines of CSS code if this instance is outside the map container.
So actually converting one external dom outside google map container into one context menu has merit that it can receive original DOM events from browser. Also using some external library can make the target behave better. As context menu, it should not only be able to handle original events, but also those events from Map.
-----------see implementations below------------------------
At the map part HTML, this is the code:
<div id="mapcover">
<div id="mapcover-map"></div> <!-- this is map container-->
<div id="demoControlPanel" class="mc-static2mapcontainer panel">I am map UI control button's container, I think one can use jQuery UI to make me look better<br><br>
<div id="zoom-in-control" class="text-center">zoomIn</div>
<div id="zoom-out-control" class="text-center">zoomOut</div>
</div>
<div id="demoContextPanel" class="mc-ascontextmenu panel">
I am map context menu container, you can sytle me and add logic to me, just as normal DOM nodes.
since I am not in Map Container at all!
<div class="text-center">
<div role="group" aria-label="..." class="btn-group">
<button id="place-marker1" type="button" class="btn btn-default">Marker1</button>
<button id="place-marker2" type="button" class="btn btn-default">Marker2</button>
</div>
</div>
<div class="form-group">
<label for="content-marker1">Content of next Marker1</label>
<input id="content-marker1" type="text" placeholder="New of Marker1!" class="form-control">
</div>
</div>
</div>
It shows how one developer can convert one external DOM (id=demoContextPanel) into one map context menu by just adding one css class ".mc-ascontextmenu"!
That pages uses mapcover.js, which helps developer to manage some key components of Map such as Map control UIs, context menu, and customized markers. Then Developers have full freedom to style its map UIs.
If you need more, you can go to its Github see readme.md: https://github.com/bovetliu/mapcover