Why does Leaflet.LayerGroup.Collision not work with my L.GeoJSON? - javascript

Trying to use the plugin "Leaflet.LayerGroup.Collision.js". I can't see what the error is as it is supposed to hide text when there is a collision. All text is showing but still collide into each other and looks pretty messy on the map.
What can be wrong in the below sample? I have tried to follow the instructions as good as possible but there seems to be something missing!
var point_txt = new L.layerGroup();
function filt_point(feature) {
if (feature.properties.size === "villages") return true;
}
var collisionLayer = L.LayerGroup.collision({ margin: 8 });
$.getJSON("/data/city_villages.geojson", function(json) {
var pointLayer = L.geoJSON.collision(null, {
filter: filt_point,
pointToLayer: function(feature, latlng) {
label = String(('<span class="textLabelclassmall">' + feature.properties.Namn + '</span>');
return new L.marker(latlng, {
icon: createLabelIcon("textLabelclasssmall", label)
});
}
});
var createLabelIcon = function(labelClass, labelText) {
return L.divIcon({
className: labelClass,
html: labelText
});
};
pointLayer.addData(json);
collisionLayer.addLayer(pointLayer);
collisionLayer.addTo(point_txt);
});
style.css:
.textLabelclassmall{
left: 1px;
top: -10px;
background-color: transparent;
display: inline-block;
text-align: left;
white-space:nowrap;
letter-spacing: 0.1em;
font-weight: 500;
font-size: 0.5vw;
}

An instance of Leaflet.LayerGroup.Collision expects the layers added to itself to be L.Markers, and not instances of L.LayerGroup (or instances of derived classes such as L.GeoJSON) - it is simply not prepared for that use case.
Add the individual markers as they are being created, or consider using L.GeoJSON.Collision instead.

I think I find a solution by using background-colourin style.css with transparent and use <span>tag for the label in the js code above. I have updated to the working solution for the code above.

Related

Uncaught TypeError: Cannot read property 'setProperty' of undefined

I tried to find an answer to this question all over the internet. I found one similar question, but it seems no one was able to give the right answer.
I was trying out an example from the Google Maps API website https://developers.google.com/maps/documentation/javascript/combining-data. I only made a few changes to your code in order to use census data for a different country.
I changed the links so that it would access my own json files. In region.geojson you have all the coordinates for Turkish provinces and in test.json you have the census data for each province.
I put the census data in the same format but when the loadCensusData function hits
map.data
.getFeatureById(stateId)
.setProperty('census_variable', censusVariable);
while iterating it gives me the following error, "Uncaught TypeError: Cannot read property 'setProperty' of undefined".
If I comment this out it will proceed the iteration until it ends but the problem is that I will not be able to use 'census_variable' as it has not been set.
4. If I change the links to point back to the US census data, it works again as expected.
Any ideas how I can solve this error? Or do you know what is causing this?
Please see the code for my map below:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="initial-scale=1.0, user-scalable=no" />
<title>Mashups with google.maps.Data</title>
<style>
html, body, #map { height: 100%; margin: 0; padding: 0; overflow: hidden; }
.nicebox {
position: absolute;
text-align: center;
font-family: "Roboto", "Arial", sans-serif;
font-size: 13px;
z-index: 5;
box-shadow: 0 4px 6px -4px #333;
padding: 5px 10px;
background: rgb(255,255,255);
background: linear-gradient(to bottom,rgba(255,255,255,1) 0%,rgba(245,245,245,1) 100%);
border: rgb(229, 229, 229) 1px solid;
}
#controls {
top: 10px;
left: 110px;
width: 360px;
height: 45px;
}
#data-box {
top: 10px;
left: 500px;
height: 45px;
line-height: 45px;
display: none;
}
#census-variable {
width: 360px;
height: 20px;
}
#legend { display: flex; display: -webkit-box; padding-top: 7px }
.color-key {
background: linear-gradient(to right,
hsl(5, 69%, 54%) 0%,
hsl(29, 71%, 51%) 17%,
hsl(54, 74%, 47%) 33%,
hsl(78, 76%, 44%) 50%,
hsl(102, 78%, 41%) 67%,
hsl(127, 81%, 37%) 83%,
hsl(151, 83%, 34%) 100%);
flex: 1;
-webkit-box-flex: 1;
margin: 0 5px;
text-align: left;
font-size: 1.0em;
line-height: 1.0em;
}
#data-value { font-size: 2.0em; font-weight: bold }
#data-label { font-size: 2.0em; font-weight: normal; padding-right: 10px; }
#data-label:after { content: ':' }
#data-caret { margin-left: -5px; display: none; font-size: 14px; width: 14px}
</style>
</head>
<body>
<div id="controls" class="nicebox">
<div>
<select id="census-variable">
<option value="test">Fertility rate 2015</option>
<option value="https://storage.googleapis.com/mapsdevsite/json/DP05_0017E">Median age</option>
</select>
</div>
<div id="legend">
<div id="census-min">min</div>
<div class="color-key"><span id="data-caret">◆</span></div>
<div id="census-max">max</div>
</div>
</div>
<div id="data-box" class="nicebox">
<label id="data-label" for="data-value"></label>
<span id="data-value"></span>
</div>
<div id="map"></div>
<script>
var mapStyle = [{
'stylers': [{'visibility': 'off'}]
}, {
'featureType': 'landscape',
'elementType': 'geometry',
'stylers': [{'visibility': 'on'}, {'color': '#fcfcfc'}]
}, {
'featureType': 'water',
'elementType': 'geometry',
'stylers': [{'visibility': 'on'}, {'color': '#bfd4ff'}]
}];
var map;
var censusMin = Number.MAX_VALUE, censusMax = -Number.MAX_VALUE;
function initMap() {
// load the map
map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 40, lng: -100},
zoom: 4,
styles: mapStyle
});
// set up the style rules and events for google.maps.Data
map.data.setStyle(styleFeature);
map.data.addListener('mouseover', mouseInToRegion);
map.data.addListener('mouseout', mouseOutOfRegion);
// wire up the button
var selectBox = document.getElementById('census-variable');
google.maps.event.addDomListener(selectBox, 'change', function() {
clearCensusData();
loadCensusData(selectBox.options[selectBox.selectedIndex].value);
});
// state polygons only need to be loaded once, do them now
loadMapShapes();
}
/** Loads the state boundary polygons from a GeoJSON source. */
function loadMapShapes() {
// load US state outline polygons from a GeoJson file
map.data.loadGeoJson('region.geojson', { idPropertyName: 'meso:name_local' });
// wait for the request to complete by listening for the first feature to be
// added
google.maps.event.addListenerOnce(map.data, 'addfeature', function() {
google.maps.event.trigger(document.getElementById('census-variable'),
'change');
});
}
/**
* Loads the census data from a simulated API call to the US Census API.
*
* #param {string} variable
*/
function loadCensusData(variable) {
// load the requested variable from the census API (using local copies)
var xhr = new XMLHttpRequest();
xhr.open('GET', variable + '.json');
xhr.onload = function() {
var censusData = JSON.parse(xhr.responseText);
censusData.shift(); // the first row contains column names
censusData.forEach(function(row) {
var censusVariable = parseFloat(row[0]);
var stateId = row[1];
console.log('StateId row[0] '+censusVariable);
console.log('censusVariable row[1] '+stateId);
// keep track of min and max values
if (censusVariable < censusMin) {
censusMin = censusVariable;
}
if (censusVariable > censusMax) {
censusMax = censusVariable;
}
// update the existing row with the new data
map.data
.getFeatureById(stateId)
.setProperty('census_variable');
});
// update and display the legend
document.getElementById('census-min').textContent =
censusMin.toLocaleString();
document.getElementById('census-max').textContent =
censusMax.toLocaleString();
};
xhr.send();
}
/** Removes census data from each shape on the map and resets the UI. */
function clearCensusData() {
censusMin = Number.MAX_VALUE;
censusMax = -Number.MAX_VALUE;
map.data.forEach(function(row) {
row.setProperty('census_variable', undefined);
});
document.getElementById('data-box').style.display = 'none';
document.getElementById('data-caret').style.display = 'none';
}
/**
* Applies a gradient style based on the 'census_variable' column.
* This is the callback passed to data.setStyle() and is called for each row in
* the data set. Check out the docs for Data.StylingFunction.
*
* #param {google.maps.Data.Feature} feature
*/
function styleFeature(feature) {
var low = [5, 69, 54]; // color of smallest datum
var high = [151, 83, 34]; // color of largest datum
// delta represents where the value sits between the min and max
var delta = (feature.getProperty('census_variable') - censusMin) /
(censusMax - censusMin);
var color = [];
for (var i = 0; i < 3; i++) {
// calculate an integer color based on the delta
color[i] = (high[i] - low[i]) * delta + low[i];
}
// determine whether to show this shape or not
var showRow = true;
if (feature.getProperty('census_variable') == null ||
isNaN(feature.getProperty('census_variable'))) {
showRow = false;
}
var outlineWeight = 0.5, zIndex = 1;
if (feature.getProperty('state') === 'hover') {
outlineWeight = zIndex = 2;
}
return {
strokeWeight: outlineWeight,
strokeColor: '#fff',
zIndex: zIndex,
fillColor: 'hsl(' + color[0] + ',' + color[1] + '%,' + color[2] + '%)',
fillOpacity: 0.75,
visible: showRow
};
}
/**
* Responds to the mouse-in event on a map shape (state).
*
* #param {?google.maps.MouseEvent} e
*/
function mouseInToRegion(e) {
// set the hover state so the setStyle function can change the border
e.feature.setProperty('state', 'hover');
var percent = (e.feature.getProperty('census_variable') - censusMin) /
(censusMax - censusMin) * 100;
// update the label
document.getElementById('data-label').textContent =
e.feature.getProperty('NAME');
document.getElementById('data-value').textContent =
e.feature.getProperty('census_variable').toLocaleString();
document.getElementById('data-box').style.display = 'block';
document.getElementById('data-caret').style.display = 'block';
document.getElementById('data-caret').style.paddingLeft = percent + '%';
}
/**
* Responds to the mouse-out event on a map shape (state).
*
* #param {?google.maps.MouseEvent} e
*/
function mouseOutOfRegion(e) {
// reset the hover state, returning the border to normal
e.feature.setProperty('state', 'normal');
}
</script>
<script async defer
src="https://maps.googleapis.com/maps/api/js?key=API_KEY&callback=initMap">
</script>
</body>
</html>
Here is my JSON file with Census Data (test.json):
[["DP02_0066PE","region"],
["2.31", "Adana"],
["2.86", "Adıyaman"],
["2.06", "Afyonkarahisar"],
["3.80", "Ağrı"],
["2.24", "Aksaray"],
["1.79", "Amasya"],
["1.76", "Ankara"],
["1.96", "Antalya"],
["2.15", "Ardahan"],
["1.73", "Artvin"],
["1.85", "Aydın"],
["1.64", "Balıkesir"],
["1.58", "Bartın"],
["3.24", "Batman"],
["2.07", "Bayburt"],
["1.71", "Bilecik"],
["2.42", "Bingöl"],
["3.40", "Bitlis"],
["1.59", "Bolu"],
["1.77", "Burdur"],
["1.91", "Bursa"],
["1.53", "Çanakkale"],
["1.77", "Çankırı"],
["1.83", "Çorum"],
["1.84", "Denizli"],
["3.21", "Diyarbakır"],
["1.85", "Düzce"],
["1.53", "Edirne"],
["1.97", "Elazığ"],
["1.82", "Erzincan"],
["2.51", "Erzurum"],
["1.55", "Eskişehir"],
["3.15", "Gaziantep"],
["1.62", "Giresun"],
["1.66", "Gümüşhane"],
["2.69", "Hakkâri"],
["2.70", "Hatay"],
["3.04", "Iğdır"],
["1.70", "Isparta"],
["1.88", "İstanbul"],
["1.72", "İzmir"],
["2.61", "Kahramanmaraş"],
["1.56", "Karabük"],
["2.10", "Karaman"],
["2.64", "Kars"],
["1.62", "Kastamonu"],
["2.18", "Kayseri"],
["2.92", "Kilis"],
["1.71", "Kırıkkale"],
["1.54", "Kırklareli"],
["1.79", "Kırşehir"],
["2.06", "Kocaeli"],
["2.18", "Konya"],
["1.58", "Kütahya"],
["2.01", "Malatya"],
["1.92", "Manisa"],
["3.41", "Mardin"],
["2.15", "Mersin"],
["1.75", "Muğla"],
["3.45", "Muş"],
["1.96", "Nevşehir"],
["2.18", "Niğde"],
["1.81", "Ordu"],
["2.47", "Osmaniye"],
["1.78", "Rize"],
["1.92", "Sakarya"],
["1.80", "Samsun"],
["4.38", "Şanlıurfa"],
["3.55", "Siirt"],
["1.72", "Sinop"],
["4.01", "Şırnak"],
["1.96", "Sivas"],
["1.93", "Tekirdağ"],
["1.72", "Tokat"],
["1.85", "Trabzon"],
["1.69", "Tunceli"],
["1.73", "Uşak"],
["3.36", "Van"],
["1.77", "Yalova"],
["1.99", "Yozgat"],
["1.55", "Zonguldak"]]
The other file with all the geoJSON data is way too big to post here. But if necessary I can find a way to share it with you.
I finally figured it out. To be frank, it was a pretty stupid mistake from my part. I guess I was already tired and didn't realize my mistake so I'd like to thank everyone who commented on my question as it helped me realize what I did wrong.
There are two JSON files. One that contains the coordinates, let's call it coordinates.geojson and the other contains the census data, let's call it census.json.
Now for these lines of code to work,
map.data
.getFeatureById(stateId)
.setProperty('census_variable');
you must make sure that stateId corresponds to both the id in the census.json file as well as the id in the coordinates.geojson file.
In my case the stateId from the census.json file did not equal the first id/state in coordinates.geojson which was Aydin. Whereas census.json started with Adana, then Adıyaman etc.
Therefore I had to change the order of the census data to
[["DP02_0066PE","region"],
["1.85", "Aydin"],
["1.72", "Izmir"],
["1.64", "Balikesir"],
["1.53", "Çanakkale"],
["1.53", "Edirne"],
["1.54", "Kirklareli"],
["1.93", "Tekirdag"],
["1.71", "Bilecik"],
["1.91", "Bursa"],
["1.88", "Istanbul"],
["2.06", "Kocaeli"],
["1.92", "Sakarya"],
["1.59", "Bolu"],
["1.55", "Eskisehir"],
["1.62", "Kastamonu"],
["1.96", "Antalya"],
["2.06", "Afyon"],
["1.77", "Burdur"],
["1.84", "Denizli"],
["1.70", "Isparta"],
["1.58", "Kütahya"],
["1.92", "Manisa"],
["1.75", "Mugla"],
["2.86", "Adiyaman"],
["1.97", "Elazig"],
["2.61", "Kahramanmaras"],
["2.01", "Malatya"],
["2.15", "Içel"],
["1.79", "Kirsehir"],
["2.18", "Kayseri"],
["1.96", "Nevsehir"],
["2.70", "Hatay"],
["1.79", "Amasya"],
["1.83", "Çorum"],
["1.62", "Giresun"],
["1.81", "Ordu"],
["1.72", "Sinop"],
["1.96", "Sivas"],
["1.80", "Samsun"],
["1.72", "Tokat"],
["1.73", "Artvin"],
["2.51", "Erzurum"],
["1.82", "Erzincan"],
["1.78", "Rize"],
["1.85", "Trabzon"],
["3.80", "Agri"],
["2.42", "Bingöl"],
["3.21", "Diyarbakir"],
["3.45", "Mus"],
["3.40", "Bitlis"],
["1.76", "Ankara"],
["1.77", "Çankiri"],
["1.55", "Zonguldak"],
["2.18", "Konya"],
["2.10", "Karaman"],
["1.73", "Usak"],
["3.15", "Gaziantep"],
["4.38", "Sanliurfa"],
["2.31", "Adana"],
["1.71", "Kirikkale"],
["2.18", "Nigde"],
["2.24", "Aksaray"],
["1.99", "Yozgat"],
["1.66", "Gümüshane"],
["2.07", "Bayburt"],
["2.64", "Kars"],
["3.41", "Mardin"],
["3.24", "Batman"],
["3.55", "Siirt"],
["4.01", "Sirnak"],
["1.69", "Tunceli"],
["2.69", "Hakkari"],
["3.36", "Van"],
["2.15", "Ardahan"],
["3.04", "Igdir"],
["2.92", "Kilis"],
["2.47", "Osmaniye"],
["1.77", "Yalova"],
["1.85", "Düzce"],
["1.56", "Karabük"],
["1.58", "Bartin"]]
Last but not least, please note that I removed all the specific Turkish characters such as ğ, ı and ş because the id from coordinates.geojson where spelled without them. Hence I changed Ağrı to Agri and so forth.
I guess it would have been better had I used numbers as IDs instead of names.
I hope this will prevent other people of making the same mistake.

How can I dynamically add a pattern image to a svg?

I'm new working with SVG images. I'm using them to paint a jacket several times of different colors without needing to have an image for each colour. This is being done with jQuery.
That was the 'easy' part. I solved it by applying fill: #color; CSS rule to <path> inside <svg>.
The hard part is when trying to fill the svg path with an image. It's really weird. My code is printed just fine in the html inside the <svg>, but not working at all. And when, in chrome's dev tools, I cut the <defs> element, and paste it again exactly where it was, it suddenly works! It's driving me crazy :(.
My code:
var items = [
{
title: 'Africa',
// color: '#776254'
},
{
title: 'Aguamarina',
// color: '#9cd3be'
},
{
title: 'Aluminio',
// color: '#9b9b9b'
},
{
title: 'Amarillo Jamaica',
// color: '#ffcd01'
},
{
title: 'Amatista',
// color: '#4d4169'
},
{
title: 'Ambar Brillante',
// color: '#eb6608'
},
{
title: 'Arándano',
// color: '#604483'
}
];
$(function () {
var PrendaShow = {
$mac_wrapper: $('#prendas-mac-slider-wrapper .mac-slider'),
$fullprints_wrapper: $('#fullprints-gallery'),
img: {
src: 'image.svg',
width: 350,
height: 188
},
init: function () {
this.makeItems();
this.bindEvents();
},
bindEvents: function () {
// events
},
makeItems: function() {
var self = this,
$model = $('<div/>', { class: 'mac-item' });
$model.append($('<div/>', { class: 'item-img' }));
$.each(items, function() {
var $item = $model.clone();
self.$mac_wrapper.append($item);
});
this.svgDraw();
},
svgDraw: function () {
var self = this;
$.get(this.img.src, function(data) {
var $svg = self.normalizeSvg(data);
self.appendImgs($svg);
});
},
normalizeSvg: function (data) {
var $svg = $(data).find('svg');
$svg = $svg.removeAttr('xmlns:a');
$svg.width(this.img.width).height(this.img.height);
return $svg;
},
appendImgs: function ($svg) {
var items = this.$mac_wrapper.find('.mac-item');
$.each(items, function() {
var $clone = $svg.clone(),
color = $(this).data('color');
$clone.find('path').css('fill', 'url(#img1)');
var img = document.createElementNS('http://www.w3.org/2000/svg', 'image');
img.setAttributeNS(null,'height','536');
img.setAttributeNS(null,'width','536');
img.setAttributeNS('http://www.w3.org/1999/xlink','href','http://www.boogdesign.com/examples/svg/daisy-grass-repeating-background.jpg');
img.setAttributeNS(null,'x','10');
img.setAttributeNS(null,'y','10');
img.setAttributeNS(null, 'visibility', 'visible')
$clone.prepend($('<defs/>').html('<pattern id="img1" patternUnits="userSpaceOnUse" width="100" height="100"></pattern>'));
$clone.find('pattern').append(img)
$(this).find('.item-img').append($clone);
});
}
};
PrendaShow.init();
});
#prendas-mac-slider-wrapper {
margin-top: 80px;
}
.mac-slider {
display: flex;
font-size: 0;
padding: 0 100px;
}
.mac-item {
width: 100%;
height: 160px;
min-width: 0;
position: relative;
display: inline-block;
text-align: center;
cursor: pointer;
}
.item-img {
background: url('https://ae01.alicdn.com/kf/HTB1Cqi6KXXXXXbpXVXXq6xXFXXXy/Classic-font-b-White-b-font-font-b-Jacket-b-font-font-b-Men-b-font.jpg') center center no-repeat;
background-size: contain;
pointer-events: none;
position: absolute;
top: 0;
left: -9999px;
right: -9999px;
margin: auto;
}
svg {
mix-blend-mode: multiply;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="hero">
<div class="container-fluid">
<div id="prendas-mac-slider-wrapper" class="row">
<div class="mac-slider">
</div>
</div>
</div>
</div>
I'm not being able to give my exact example because of images upload. I really hope you understand what I'm trying to do here, and what's going wrong.
EDIT: Not sure if this deserves to be in an answer, but I preferred to write it here since I don't think its the right way to do this.
As I knew that, from chrome dev tools, if I deleted the <defs> element and pasted it again, it worked, I tried this and worked:
var svg_html = $svg.html();
$svg.html('');
$svg.html(svg_html);
Your problem is here:
$clone.prepend($('<defs/>').html('<pattern ...
You cannot use jQuery to create SVG elements like this. You are doing it the correct way (with createElementNS()) when you create the <image> element. But you also need to use the same method when creating the <defs> and <pattern> elements.

Leaflet maerkercluster - Percentage instead of number of Children

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

Leaflet clustermarker with custom icon

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 ...

JS Maps v3: custom marker with user profile picture

I am struggling since 2 days with something I was thinking easy, on a map, I have to display a marker for each user with the user FB profile picture inside.
I am wondering how I can have a result similar to this one? What I tried was really hackish.
I put the FB picture as the marker icon
I put a CSS class on the label of the marker
I find the sibling to add this border and this arrow to decorate the user picture
but it doesn't work when there is more than one marker on the map.
.marker-labels {
display: none !important;
+ div {
background-color: $dark-gray;
border: 2px solid $dark-gray;
#include radius(0.2em);
height: 54px !important;
width: 54px !important;
overflow: inherit !important;
> img {
height: 50px;
width: 50px;
}
&:after {
content: ' ';
height: 0;
width: 0;
border: 6px solid transparent;
border-top-color: $dark-gray;
position: absolute;
top: 52px;
left: 19px;
}
}
}
global question:
how can I get an icon like that (http://mt-st.rfclipart.com/image/thumbnail/24-1d-5f/blue-glossy-square-map-pin-or-speech-bubble-Download-Royalty-free-Vector-File-EPS-29153.jpg for instance) with a custom user picture inside? is it possible?
otherwise how is it possible to customize the icon (if it is the profile picture) to have a result similar to the screenshot
thanks for your help
This answer assumes you already have the URIs for the facebook profile images. Honestly, it feels there is an easier way, but I found some code that shows how to create a custom marker with custom HTML elements and I went from there. From there's it's pretty easy to create a custom marker that accepts a image URI as a parameter. From the original, I just added an imageSrc parameter, moved the styling outside the code by attaching a class name to the new div. In terms of html and css, I just appended an image with the passed image URI into the div, and just added some CSS to make it look like what you have.
Demo
So the javascript code looks something like this:
function CustomMarker(latlng, map, imageSrc) {
this.latlng_ = latlng;
this.imageSrc = imageSrc; //added imageSrc
this.setMap(map);
}
CustomMarker.prototype = new google.maps.OverlayView();
CustomMarker.prototype.draw = function () {
// Check if the div has been created.
var div = this.div_;
if (!div) {
// Create a overlay text DIV
div = this.div_ = document.createElement('div');
// Create the DIV representing our CustomMarker
div.className = "customMarker" //replaced styles with className
var img = document.createElement("img");
img.src = this.imageSrc; //attach passed image uri
div.appendChild(img);
google.maps.event.addDomListener(div, "click", function (event) {
google.maps.event.trigger(me, "click");
});
// Then add the overlay to the DOM
var panes = this.getPanes();
panes.overlayImage.appendChild(div);
}
// Position the overlay
var point = this.getProjection().fromLatLngToDivPixel(this.latlng_);
if (point) {
div.style.left = point.x + 'px';
div.style.top = point.y + 'px';
}
};
CustomMarker.prototype.remove = function () {
// Check if the overlay was on the map and needs to be removed.
if (this.div_) {
this.div_.parentNode.removeChild(this.div_);
this.div_ = null;
}
};
CustomMarker.prototype.getPosition = function () {
return this.latlng_;
};
I think I added only one or two lines here. You can just add this to your page I think. With this in place you can just style the container as normal, and it should apply to all the custom markers. You can add elements and classes as you see fit to achieve the look you are looking for. But for completion's sake I added the styles I used for the demo here.
.customMarker { /* the marker div */
position:absolute;
cursor:pointer;
background:#424242;
width:100px;
height:100px;
/* we'll offset the div so that
the point passed doesn't end up at
the upper left corner but at the bottom
middle. so we'll move it left by width/2 and
up by height+arrow-height */
margin-left:-50px;
margin-top:-110px;
border-radius:10px;
padding:0px;
}
.customMarker:after { //triangle
content:"";
position: absolute;
bottom: -10px;
left: 40px;
border-width: 10px 10px 0;
border-style: solid;
border-color: #424242 transparent;
display: block;
width: 0;
}
.customMarker img { //profile image
width:90px;
height:90px;
margin:5px;
border-radius:2px;
}
And for the demo I have some sample data in array and placed them on the map using a for loop.
var data = [{
profileImage: "http://domain.com/image1.jpg",
pos: [37.77, -122.41],
}, {
profileImage: "http://domain.com/image2.jpg",
pos: [37.77, -122.41],
}]
for(var i=0;i<data.length;i++){
new CustomMarker(
new google.maps.LatLng(data[i].pos[0],data[i].pos[1]),
map,
data[i].profileImage
)
}
I hope that helps.

Categories