clicking a marker opening a image next to map - javascript

I have a leaflet map which has lots of markers and what I would like is to open a image next to the map which is linked to certain marker on marker click. All I know that I need javascript/jquery and ajax to make this work.
Here is an example what it could look like: http://www.washingtonpost.com/wp-srv/special/local/14th-street-businesses/
Any hints/tips/tutorials appreciated.
Thanks in advance!

Here is a solution to your problem: http://franceimage.github.io/leaflet/10/
var selectedMarker = false;
var geojsonMarkerOptions = {
radius: 8,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8
};
L.geoJson(fi_markers, {
pointToLayer: function (feature, latlng) {
var marker = L.circleMarker(latlng, geojsonMarkerOptions);
marker.on('click', function (e) {
var feature = e.target.feature;
var content = '<h3>' + feature.properties.popupContent + '</h3>' + feature.properties.thumbnail + '';
document.getElementById("events").innerHTML = content;
if(selectedMarker != false) {
selectedMarker.setStyle({ fillColor: "#ff7800"});
}
marker.setStyle({ fillColor: "#000000"});
selectedMarker = marker;
});
return marker;
}
}).addTo(map);
This a way you can do it (there are plenty others)
In this example:
There are no ajax calls (and no jquery either). Data is loaded from a geoJson structure (look at http://franceimage.github.io/leaflet/10/data.geojson)
The html content of the popup is created using the properties of the geojson feature
I have used CircleMarker so that changing the color is a piece of cake
I hope this will help

Related

Leaflet circleMarker popup is now showing

I can't seem to get CircleMarkers to show their associated popups in Leaflet. Here's what I am doing:
var markerGroup;
fetch("<FILE_NAME_REMOVED>").then(function(response){
return response.text();
}).then(function(text){
var lines = text.split("\n");
var markerArray = [];
for (var i=1; i<lines.length; i++) {
var parts = lines[i].split(",");
if (typeof parts[2] !='undefined') {
marker = new L.CircleMarker([parts[3],parts[2]], {
radius: 4,
color: 'red',
opacity: 0.95,
weight: 1.2,
dashArray: "2,3",
fillOpacity: 0.2
}).bindPopup("I am at " + parts[0] + "," + parts[1]);
markerArray.push(marker);
};
}
markerGroup = L.featureGroup(markerArray).addTo(map);
map.fitBounds(markerGroup.getBounds());
}).catch(function(err){
console.log(err);
});
The popups are just not showing up - in fact when I click on the circles the map just zooms in. If I replace L.CircleMarker with just L.Marker though it works fine, but I actually need circles.
I even tried adding an on click function to the markers, still nothing.
Could someone please help? I am using leaflet 1.3.4.
EDIT:
I didn't mention (because I did't think it mattered) that the markers are added last to the map but the map has a geoJson layer as well. I've noticed (by accident) that the popups do show up just fine over the area of the map not covered by the geoJson layer. This is strange to me because the geojson layer is set to back from the beginning:
var datalayer;
$.getJSON("<FILE_NAME_REMOVED>",function(data){
datalayer = L.geoJson(data ,{
onEachFeature: function(feature, featureLayer) {
featureLayer.setStyle({fillColor: 'white'});
featureLayer.setStyle({weight:1});
featureLayer.setStyle({fillOpacity:0.1});
featureLayer.bringToBack();
}
}).addTo(map);
map.fitBounds(datalayer.getBounds());
});

Leaflet : ordering GeoJSON elements inside a layer

I'm displaying a GeoJSON layer using leaflet, with the pointToLayer function. Everything works ok so far.
But I would like to display my points in a certain order, based on a property of the GeoJSON. This is important because the radiuses of my points varies with this property, and I need to display the smaller circles on top. I hope I make myself clear.
I've tried many things, but here's what I think is my best try :
var pointLayer = L.geoJson(centroids, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
fillColor: "#76C551",
color: "#000",
weight: 1,
fillOpacity: 1
});
},
onEachFeature: function (feature, layer) {
var radius = calcPropRadius(feature.properties.nb);
layer.setRadius(radius);
feature.zIndexOffset = 1/feature.properties.nb*1000;
layer.bindPopup(feature.properties.name + " : " + String(feature.zIndexOffset));
}
});
You can notice that the zIndexOffset of features can be read in the popups, and they look ok. But the displaying order of the circles doesn't reflect the zIndexOffset.
I've tried using the setZIndexOffset method, but as I understand it it works only with markers.
Does anyone know how to do this ? Thanks a lot for any insight !
Whereas ghybs answer works perfectly for leaflet 0.7, switching to leaflet 1.0 allows the use of panes which makes for an easier solution :
var pointLayer = L.geoJson(centroids, {
pointToLayer: function (feature, latlng) {
return L.circleMarker(latlng, {
fillColor: "#76C551",
color: "#000",
weight: 1,
fillOpacity: 1
});
},
onEachFeature: function (feature, layer) {
var radius = calcPropRadius(feature.properties.nb);
layer.setRadius(radius);
layer.setStyle({pane: 'pane'+ feature.properties.nb});
var currentPane = map.createPane('pane' + feature.properties.nb);
currentPane.style.zIndex = Math.round(1/feature.properties.nb*10000);
layer.bindPopup(feature.properties.name + " : " + String(feature.zIndexOffset));
}
});
Hope it can be of use to someone else !
As you figured out, the zIndexOffset option is only for L.marker's.
L.circleMarker's go into the overlayPane and you can re-order them one to each other using .bringToFront() and .bringToBack() methods.

Style both points and multipolygons simultaneously Leaflet

I was working on an application in which you can show points on a map. To improve the application, I added the functionality the show multipolygons as well.
I've created a dropdowmenu where you can select a dataset. One is a dataset containing stores (points), and the other contains precincts (multipolygons). Everything works fine and I can show either points or polygons on the map.
Because I build the application to only show points first, I've only styled the points (see code below).
var myStyle = {
"color": "#ff7800",
"weight": 5,
"opacity": 0.65
};
window["mapDataLayer"] = L.geoJson(geojson, {
pointToLayer: function (feature, latlng) {
var markerStyle = {
fillColor: getColor(feature.properties.Fastfoodketen),
color: "#696969",
fillOpacity: 0.6,
opacity: 0.9,
weight: 1,
radius: 8
};
return L.circleMarker(latlng, markerStyle);
},
onEachFeature: function (feature, layer){
layer.on({
click: function showResultsInDiv() {
var d = document.getElementById('tab4');
d.innerHTML = "";
for (prop in feature.properties){
d.innerHTML += prop+": "+feature.properties[prop]+"<br>";
}
$('.nav-tabs a[href="#tab4"]').tab('show');
}
}); },
style: myStyle
}).addTo(map);
Now I want to style the polygons. How can I alter the code above to include polygons as well?
I thought it would be a good way was to include an if/else loop to check if the geometry was a point or a multipolygon and then direct to the appropriate styling. However, I don't know how to check if a geometry is a point/polygon.
This is covered by the Leaflet documentation on GeoJSON options (emphasis mine):
pointToLayer: A Function defining how GeoJSON points spawn Leaflet layers.[...]
style: A Function defining the Path options for styling GeoJSON lines and polygons, [...]
You can see examples of this in the Leaflet tutorials for GeoJSON

Toggle layers on and off in Leaflet (more complex scenario)

I am using jQuery's getJSON method to load external line data I've created in QGIS.
What I'm trying to do is toggle my layers on and off - simple check boxes, no radio button for the basemap. I'd also like all the layers to be off when the map is initially loaded.
My code
var map=L.map('map').setView([41.9698, -87.6859], 12);
var basemap = L.tileLayer('http://a.tile.stamen.com/toner/{z}/{x}/{y}.png',
{
//attribution: would go here
maxZoom: 17,
minZoom: 9
}).addTo(map);
//display geoJson to the map as a vector
var x = function(source, map)
{
var layers = L.geoJson(source,
{
style: function(feature){
var fillColor, side=feature.properties.side;
if (side==='Both') fillColor = '#309e2d';
else if (side==='Neither') fillColor = '#d90f0f';
else if (side==='West Only') fillColor = '#e27f14';
else if (side==='East Only') fillColor = '#2b74eb';
else if (side==='North Only') fillColor = '#eae42b';
else if (side==='South Only') fillColor = '#552d04';
else fillColor = '#f0f5f3';
return { color: fillColor, weight: 3.5, opacity: null };
},
onEachFeature: function(feature, geojson){
var popupText="<h1 class='makebold'>Border: </h1>"+feature.properties.name+"<br/>"+"<h1 class='makebold'>Which Side?: </h1>"+feature.properties.side;
geojson.bindPopup(popupText);
}
}).addTo(map);
};
$.getJSON("data/Knox.geojson", function(source){ x(source, map); });
$.getJSON("data/abc.geojson", function(source){ x(source, map); });
$.getJSON("data/xyz.geojson", function(source){ x(source, map); });
I tried assigning a variable before the L.geoJson function (var layers), and then L.control.layers(null, layers).addTo(map); That doesn't seem to work.
How does one create a layer control for multiple external geojson's that are already associated with a few callback functions (L.geoJson, style, and onEachFeature)? Thanks in advance.
EDIT:
Since you clarified that you want just the entire collection to be switched on/off, it is even more simple (and almost like what you tried by assigning your L.geoJson to var layers), but you have to take care of asynchronous processes.
To avoid this issue, you could do something like:
var myLayerGroup = L.layerGroup(), // do not add to map initially.
overlays = {
"Merged GeoJSON collections": myLayerGroup
};
L.control.layers(null, overlays).addTo(map);
function x(source, map) {
// Merge the GeoJSON layer into the Layer Group.
myLayerGroup.addLayer(L.geoJson({}, {
style: function (feature) { /* … */ },
onEachFeature: function (feature, layer) { /* … */ }
}));
}
$.getJSON("data/Knox.geojson", function(source){
x(source, map);
});
Then myLayerGroup will be gradually populated with your GeoJSON features, when they are received from the jQuery getJSON requests and they are converted by L.geoJson.
If my understanding is correct, you would like the ability to switch on/off independently each feature from your GeoJSON data?
In that case, you would simply populate your layers object while building the L.geoJson layer group, e.g. inside the onEachFeature function:
var layers = {};
L.geoJson(source, {
style: function (feature) { /* … */ },
onEachFeature: function(feature, layer){
var popupText = "<h1 class='makebold'>Border: </h1>" +
feature.properties.name + "<br/>" +
"<h1 class='makebold'>Which Side?: </h1>" +
feature.properties.side;
layer.bindPopup(popupText);
// Populate `layers` with each layer built from a GeoJSON feature.
layers[feature.properties.name] = layer;
}
});
var myLayersControl = L.control.layers(null, layers).addTo(map);
If you have more GeoJSON data to load and to convert into Leaflet layers, simply do exactly the same (adding built layer into layers in onEachFeature function) and build the Layers Control only once at the end, or use myLayersControl.addOverlay(layer).
Note: make sure to structure your code to take into account your several asynchronous processes, if you load each GeoJSON data in a separate request. Refer to jQuery Deferred object. Or simply create your Layers Control first and use the addOverlay method.
If you want them to be initially hidden from the map, simply do not add the geoJson layer to the map…
I learned a lot more about layer control in Leaflet than I expected, which is great.
#ghybs offered really helpful suggestions.
My issue was about toggling external geoJson files on and off, particularly with the getJSON jQuery method. I was trying to assign a variable within my multiple callbacks, like:
var layers=L.geoJson(source,{
{style: /*....*/},
{onEachFeature: /*....*/}}
and then just going L.control.layers(null, layers).addTo(map);
That doesn't work (why? I still can't explain-I'm quite the beginner-programmer). The way I did get this to work was by creating my style and onEachFeature functions separately, like this:
function borders (feature){
var fillColor, side=feature.properties.side;
if (side==='Both') fillColor = '#309e2d';
else if (side==='Neither') fillColor = '#d90f0f';
else if (side==='West Only') fillColor = '#e27f14';
else if (side==='East Only') fillColor = '#2b74eb';
else if (side==='North Only') fillColor = '#eae42b';
else if (side==='South Only') fillColor = '#552d04';
else fillColor = '#f0f5f3';
return { color: fillColor, weight: 3.5, opacity: null };
};
and
function popUp (feature, geojson){
var popupText="<h1 class='makebold'>
Border: </h1>"+feature.properties.name+"<br/>"+"<h1 class='makebold'>
Which Side</h1>"+feature.properties.side;geojson.bindPopup(popupText);
};
and then assigning these directly as callbacks into the getJSON method. By doing it this way, I could create a variable before "drawing" my geoJson to the map with L.geoJson(). Then I could assign the variable dynamically(?) to the layer control:
$.getJSON("data/xyz.geojson", function(source){
var xyz = L.geoJson(source, {
style: borders,
onEachFeature: popUp});
togglelayer.addOverlay(xyz, 'This name shows up on the control')});
});
I stored the variable togglelayer like this:
var togglelayer = L.control.layers(null, null,{collapsed: false}).addTo(map);
This post was also helpful: How to add two geoJSON feature collections in to two layer groups

how to improve Google map loading time?

My website has a number of pages that show a Google map with a bunch of markers, here's an example.
As you can see, the maps take a long time to load and I'm looking for ways to improve this. I was hoping to use GeoWebCache to cache the map tiles on the server, but I was informed that this would violate the terms of use for Google maps.
The code that I use to display a map and add a marker is appended below. It's a pretty straightforward usage of the Google Maps V3 JavaScript API, so I don't think there's much scope for optimizing it. Are there any obvious steps I could take to reduce the map-loading time?
SF.Map = function(elementId, zoomLevel, center, baseImageDir) {
this._baseImageDir = baseImageDir;
var focalPoint = new google.maps.LatLng(center.latitude, center.longitude);
var mapOptions = {
streetViewControl: false,
zoom: zoomLevel,
center: focalPoint,
mapTypeId: google.maps.MapTypeId.ROADMAP,
mapTypeControl: false
};
this._map = new google.maps.Map(document.getElementById(elementId), mapOptions);
this._shadow = this._getMarkerImage('shadow.png');
};
SF.Map.prototype._getMarkerImage = function(imageFile) {
return new google.maps.MarkerImage(this._baseImageDir + '/map/' + imageFile);
};
SF.Map.prototype._addField = function(label, value) {
return "<span class='mapField'><span class='mapLabel'>" + label + ": </span><span class='mapValue'>" + value + "</span></span>";
};
/**
* Add a marker to the map
* #param festivalData Defines where the marker should be placed, the icon that should be used, etc.
* #param openOnClick
*/
SF.Map.prototype.addMarker = function(festivalData, openOnClick) {
var map = this._map;
var markerFile = festivalData.markerImage;
var marker = new google.maps.Marker({
position: new google.maps.LatLng(festivalData.latitude, festivalData.longitude),
map: map,
title: festivalData.name,
shadow: this._shadow,
animation: google.maps.Animation.DROP,
icon: this._getMarkerImage(markerFile)
});
var bubbleContent = "<a class='festivalName' href='" + festivalData.url + "'>" + festivalData.name + "</a><br/>";
var startDate = festivalData.start;
var endDate = festivalData.end;
if (startDate == endDate) {
bubbleContent += this._addField("Date", startDate);
} else {
bubbleContent += this._addField("Start Date", startDate) + "<br/>";
bubbleContent += this._addField("End Date", endDate);
}
// InfoBubble example page http://google-maps-utility-library-v3.googlecode.com/svn/trunk/infobubble/examples/example.html
var infoBubble = new InfoBubble({
map: map,
content: bubbleContent,
shadowStyle: 1,
padding: 10,
borderRadius: 8,
borderWidth: 1,
borderColor: '#2c2c2c',
disableAutoPan: true,
hideCloseButton: false,
arrowSize: 0,
arrowPosition: 50,
arrowStyle: 0
});
var mapEvents = google.maps.event;
// either open on click or open/close on mouse over/out
if (openOnClick) {
var showPopup = function() {
if (!infoBubble.isOpen()) {
infoBubble.open(map, marker);
}
};
mapEvents.addListener(marker, 'click', showPopup);
} else {
mapEvents.addListener(marker, 'mouseover', function() {
infoBubble.open(map, marker);
});
mapEvents.addListener(marker, 'mouseout', function() {
infoBubble.close();
});
}
};
You could try "lazy loading" the google map, something like this:
var script=document.createElement("script");
script.type="text/javascript";
script.async=true;
script.src="http://maps.google.com/maps/api/js?sensor=false&callback=handleApiReady";
document.body.appendChild(script);
Or even like this is how I go it for Facebook AIP so that this doesn't slow down the initial load time.
$.getScript('http://connect.facebook.net/en_US/all.js#xfbml=1', function() {
FB.init({appId: opt.facebook_id, status: true, cookie: true, xfbml: true});
});
Example: http://blog.rotacoo.com/lazy-loading-instances-of-the-google-maps-api
Also looking at your HTML you have a lot of JS which should be in an external JS file and not inline, can you not pass a array instead of having a lot of duplicate inline code.
One option is to take a static snapshot and create the map behind it. once map is fully loaded, replace the dynamic one with the static map.
I also suggest that you take a look at:
https://developers.google.com/maps/documentation/staticmaps/
it might be that you can provide a simpler solution to your site without using the full dynamic maps api.

Categories

Resources