I'm trying to create a small example using Leaflet and use it on my web app, however I'm having some dificulties.
I already made a standalone Leaflet example from the quick start guide of their page, which has a static map div and overlays a few objects (marker, circle and polygon).
However in my app, the map div container is positioned and sized dynamically with jquery, and it's a pretty complex app so I can't post here the whole code.
The problem is that after the correct processing of the div the map is drawn correctly in the div (wherever it is), however in the overlays layer I can only see the marker. The circle and polygonare not visible.
var map = L.map(this.$el.attr('id')).setView([51.505, -0.09], 13);
L.tileLayer('http://{s}.tile.cloudmade.com/<APIKEY>/997/256/{z}/{x}/{y}.png', {
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade',
maxZoom: 18
}).addTo(map);
var circle = L.circle([51.508, -0.11], 500, {
color: 'red',
fillColor: '#f03',
fillOpacity: 0.5
}).addTo(map);
var polygon = L.polygon([
[51.509, -0.08],
[51.503, -0.06],
[51.51, -0.047]
]).addTo(map);
var marker = L.marker([51.5, -0.09]).addTo(map);
marker.bindPopup("<b>Hello world!</b><br>I am a popup.");
circle.bindPopup("<b>Hello world!</b><br>I am a popup.").openPopup();
I believe there's nothing wrong in the way I create the map and objects and I even call map.invalidateSize() after the whole resizing of the div. And the curious part is that the popup is shown in the correct position, but no circle behind it.
Could the problem be in the way I resize and position the div containers. Is there any specific CSS property I should be using in the div container?
Thanks for all the help.
After sleeping on this problem I thought about making a JSFiddle example to reproduce the issue.
However no matter how hard I tried I couldn't replicate the issue in a simpler standalone example, so the problem had to be in some conflict within my app.
Every symptom pointed to a CSS problem, however my CSS was well scoped.
So my last theory was that it had to be a conflict in some library I was using.
It turned out to be correct. The problem was a conflict in the CSS stylesheet of the library NVD3
Related
I'm using LeafletJS to display maps in a number of places on my site.
In most cases, it is working fine, but on one specific page I am getting a weird rendering glitch, which looks like this:
As you can see, the map is rendering with vertical grey bars at each tile boundary. Additionally, if I add markers to the map, they also display squashed.
The weird thing is that I'm using the same code (ie the exact same JS file) to render similar maps on other pages without any problems. I'm also using similar code to render two other maps on the same page, again without problems.
The JS code looks like this:
(function(L) {
var element = document.querySelector('.details-map');
if (!element) { return; }
var coords = element.getAttribute('data-coords');
var latlng = coords.split(',');
var location = {lat: parseFloat(latlng[0]), lng: parseFloat(latlng[1])};
var zoom = element.getAttribute('data-zoom');
var map = new L.Map(element, {center: location, zoom: zoom, gestureHandling: true});
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors',
}).addTo(map);
})(L);
And the associated HTML looks like this:
<div class="details-map" data-coords="51.5216,-0.71998" data-zoom="14"></div>
The rendering glitch occurs at all zoom levels and persists after the doing things like map.invalidateSize() to force a redraw.
Can anyone explain why I'm getting this glitch and how to fix it? Thank you.
After much digging, I finally resolved this.
The problem was some CSS that was linked to an element some distance further up the DOM tree, and not really relevant to the map. It looks like this:
.local-branch img {
vertical-align: top;
display: inline-block;
width: 25%;
padding: 0 10px;
}
The intention of this CSS is to lay out a set of images related to the branch. The map is being added later as part of the branch details, within the .local-branch element. The styles are obviously not intended to apply to the map, but are applying anyway as the CSS selector is too broad.
I'll need to find a way to fix it that doesn't break the existing image layout, which still needs some thought, but at least I've worked out what was going on, so the mystery is solved.
I have a Leaflet map with a geoJSON containing contour lines. The elevation corresponding to each line is located in feature.properties.Elevation of the geoJSON. I want to achieve the following:
What I've tried to achieve this in leaflet is to calculate the center of each polyline and then add a marker to that position containing the data from feature.properties.Elevation.
L.geoJson(contourJSON, {
onEachFeature: function(feature, layer) {
var label = L.marker(layer.getBounds().getCenter(), {
icon: L.divIcon({
className: 'label',
html: feature.properties.Elevation,
iconSize: [100, 40]
})
}).addTo(map);
}
});
Which kind of works, but does not have a nice styling and it's not really clear which value belongs to a specific line:
What would be a better method of adding the elevation labels in such a way that it is readable and maybe dynamic to the current zoom level? I'm using Leaflet 1.0.3 so maybe Tooltip could offer a solution? Thanks!
You might be interested in those Leaflet plugins, possibly combined: (not sure how easy it would be to combine them)
Leaflet.LabelTextCollision (demo)
…display[s] labels on vector data while avoiding label collisions.
Leaflet.TextPath (demo)
Shows a text along a Polyline.
You might also want to check out the rest of Leaflet plugins.
BTW, I am not sure placing your label / marker at the polyline "center" is appropriate. You might just pick one of its vertices, or for example the farthest to the right / East to have an effect similar to the example you provide.
So I'm using the latest version of leaflet (v1.0.2), and am trying to dynamically apply text labels to specific lat lng points on a custom (geo aligned) map.
My issue is that I need the text on the map to maintain it's the size (as though the text is actually part of the tile image) when zooming. Using a Marker of any kind results in the text staying at its correct size. If I use something like an image overlay and add an SVG with text in, it scales with the map zoom.
I've noticed that the image overlay has a CSS3 scale added to its transform property when zooming whereas the marker does not.
Can I extend the marker to scale as the image overlay does?
I've already written code that listens to the zoom event and adjusts the font size of markers but this is CPU intensive (especially for mobile browsers) and I don't really want to render the text dynamically within svgs either!
I've provided a demo so that this makes more sense. You can see that example_1 (the marker) maintains it's size however far you zoom in or out. Example_2 (the svg image) scales relative to the map when zooming. This (Example_2) is what I'm trying to get an L.DivIcon with html text content to do!
Any help or suggestions are appreciated!
https://jsfiddle.net/z96L7hdu/
Example Code
HTML
<div id="map" style="width:500px; height:600px;"></div>
JavaScript
var map = L.map('map', {
zoomSnap: 0
}).setView([0, 0], 3);
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
var img = "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciICAgICB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiB2aWV3Qm94PSIwIDAgNTAwIDQwIj4gIDx0ZXh0IHg9IjAiIHk9IjAiIGZvbnQtZmFtaWx5PSJWZXJkYW5hIiBmb250LXNpemU9IjM1Ij4gICAgRXhhbXBsZV8yICA8L3RleHQ+PC9zdmc+";
imageBounds = [[-8.636810901898114, -12.135975261193327], [-18.28136415046407, 17.181122017133486]];
L.imageOverlay(img, imageBounds).addTo(map);
var myIcon = L.divIcon({className: 'my-div-icon', html:"Example_1"});
L.marker({lat: 0.7800052024755708, lng: 0.010986328125}, {icon: myIcon}).addTo(map);
Apologies for the late answer, but I thought it was an interesting question. You can indeed extend the L.Marker class, to create markers that resize the font of their DivIcon to match the zoom level:
L.FixedSizeMarker = L.Marker.extend({
options: {
fontSize: 12, // starting size of icon in pixels
zoomBase: 3 // Zoom level where fontSize is the correct size
},
update: function () {
if (this._icon && this._icon.tagName === 'DIV' && this._map) {
let size = this.options.fontSize * Math.pow(2, (this._map.getZoom() - this.options.zoomBase));
this._icon.style.fontSize = size + 'px';
}
return L.Marker.prototype.update.call(this);
}
});
L.fixedSizeMarker = (latlng, options) => new L.FixedSizeMarker(latlng, options);
The code above defines a new FixedSizeMarker, which behaves just like a normal Marker, but if you add a DivIcon to it, it will resize the font. It takes two options, to specify the font size in pixels, and the zoom level that you want that font size to be correct for. In the example in the OP's JSFiddle, you would use it like this:
var myIcon3 = L.divIcon({className: 'my-div-icon', html:"Example_3"});
L.fixedSizeMarker({lat: 0.7800052024755708, lng: -12.135975261193327},
{icon: myIcon3, fontSize: 24, zoomBase: 3}).addTo(map);
When using these markers, it may look better to set {markerZoomAnimation: false} in the map options. The marker size change is otherwise quite obvious when you zoom the map.
const element = marker.getElement();
element.style[L.DomUtil.TRANSFORM] = `scale(3)`;
You can use this to modify the css of a marker
Im using Leaflet & Geojson-vt to visualize a large dataset (GeoJSON with 70,000 polylines features)
Is there any way to show smoothly a popup (it contains the polyline data) on click event on one of the multiple polylines ?
I am using geojson-vt and the example from here to add the tiles to the leaflet map.
i tried this
function onEachFeature(feature, layer) {
layer.on('click', function(e) {
layer.bindPopup(feature.properties.NUMERO);
});
}
L.geoJson($scope.dataOfFile.data, {
onEachFeature: onEachFeature
}).addTo(map);
but the map keep freezing.
Not sure I'm understanding... That example draws a canvas layer from vector tiles, this has no interaction method. If it were polygons you could use point in polygon from the original data. Since it's lines you may want to experiment with the almost over plugin using 0 weight (transparent) lines.
I had a similar issue with popups on Markers. If I added the popup before the marker was added to the map then the map would briefly freeze when all the markers were added to the map.
So instead I added the Markers to the map first. I maintained an array of all the markers then added the popups afterwards to each marker and I no longer experienced the map locking up.
I am trying to create a presentation running on top of Reveal.js, which would include a Leaflet.js map within one of the slides. I have included all necessary Javascript & CSS files into my Reveal.js presentation and I can make the map appear on the slide.
However, the problem is: map tiles are not displayed correctly. Instead of the actual map tiles, all I am seeing is gray background and some horizontal black lines. I can zoom in/out and pan the map, and the black lines are moving accordingly.
There are no error message in the Javascript console, and the browser seems to be downloading map tiles from server exactly as it should. I believe the problem has something to do with the CSS code of Leaflet map tiles - .leaflet-tile within leaflet.css - being somehow incompatible with Reveal.js.
The question is: Does anyone know how to get around this issue? Or is it a deadend with no possible solution?
I have the following CSS for the <div id="map">:
#map {
height:400px;
width:100%;
}
EDIT: One obvious workaround for this is to use <iframe> tag to embed the map into the presentation. Seems to work just fine, and maybe it is better to keep the frameworks separated. However, the downside is that if there are several maps in the presentation, each within its own <iframe>, a copy of Leaflet.js is loaded to memory for each and every iframe.
EDIT #2: A better solution, it seems, is to use Polymaps instead of Leaflet.js. It seems that several Polymaps maps can be embedded into a reveal.js presentaion. No issues.
I found it easily to do it with a web component, this way, the shadow dom will protect my leaflet map from the evil hands of reveals css
here is a repo with an example
<link rel="import" href="./leaflet-map.html">
...
<div class="reveal">
<div class="slides">
<section data-state="map">
<leaflet-map></leaflet-map>
</section>
</div>
</div>
Here is the web component
<template id="leaflet-map-template">
<link rel="stylesheet" href="./bower_components/leaflet/dist/leaflet.css">
<div id="mapid" style="height: 500px"></div>
<!-- LEAFLET JS -->
</template>
<script src="./bower_components/leaflet/dist/leaflet.js"></script>
<script>
class LeafletMap extends HTMLElement {
constructor () {
super();
let tmpl = document.currentScript.ownerDocument.querySelector('template')
let shadowRoot = this.attachShadow({mode: 'open'})
shadowRoot.appendChild(tmpl.content.cloneNode(true))
let mapDiv = this.shadowRoot.getElementById('mapid')
this.map = L.map(mapDiv).setView([19.39682052576622, -99.13478851318361], 13)
// this.setAttribute('map', map)
// Tiles de open street maps
//L.tileLayer("https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png").addTo(map)
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpejY4NXVycTA2emYycXBndHRqcmZ3N3gifQ.rJcFIG214AriISLbB6B5aw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(this.map)
let myIcon = L.icon({
iconUrl: './lentes.png',
iconSize: [40, 40], // size of the icon
iconAnchor: [20, 20], // point of the icon which will correspond to marker's location
tooltipAnchor: [20,0]
})
L.marker(
[19.418657758792698, -99.14065182209016],
{icon: myIcon}
).bindTooltip('Ranchito').addTo(this.map)
}
resize() {
this.map.invalidateSize()
}
}
window.customElements.define('leaflet-map', LeafletMap)
</script>
It might be happening because the #map element is hidden (due to the hidden slide) when it is initialized, so it cannot read the dimensions..
Try using map.invalidateSize(false); once your slide becomes visible..
Reveal.addEventListener( 'slidechanged', function( event ) {
// event.previousSlide, event.currentSlide, event.indexh, event.indexv
if (event.indexh == 5){ // assuming your 5th slide is the one with the map
map.invalidateSize(false); // assuming that map holds the the reference to your leaflet instance
}
} );