I'm trying to use leaflet inside of a web component. Here's my code.
I've copy pasted the standard example on leaflet website, but given it a HTMLElement rather than id string.
class InfoWithMap extends HTMLElement {
constructor(){
super().attachShadow({mode: 'open'});
}
connectedCallback(){
let container = `
<style>
#map {
height: 500px;
width: 100%;
}
</style>
<div id='container'>
<div>Name: Someone</div>
<div>Location: Some place</div>
<div id='map'></div>
</div>`
this.shadowRoot.innerHTML = container;
let mapDiv = this.shadowRoot.getElementById('map');
var map = L.map(mapDiv).setView([51.505, -0.09], 19);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 19,
attribution: '© OpenStreetMap'
}).addTo(map);
}
}
window.customElements.define('info-with-map', InfoWithMap);
let m = document.createElement('info-with-map');
document.getElementById('main').append(m);
#main {
width: 50%;
}
<!-- https://leafletjs.com/examples/quick-start/ -->
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.9.3/dist/leaflet.css"
integrity="sha256-kLaT2GOSpHechhsozzB+flnD+zUyjE2LlfWPgU04xyI="
crossorigin=""/>
<script src="https://unpkg.com/leaflet#1.9.3/dist/leaflet.js"
integrity="sha256-WBkoXOwTeyKclOHuWtc+i2uENFpDZ9YPdf5Hf+D7ewM="
crossorigin=""></script>
<div id="main">
</div>
However, as you can see the map is not being shown properly.
Any idea what is wrong with the setup?
Edit: It may not be easy to see the problem in the limited space of inline output here. Hence, here are the codepens to compare outputs of Leaflet with and without web components.
Without web components
With web components
Notice in the output of web component, the tiles are not displayed properly.
You are adding the Leaflet CSS to the global scope; but global CSS can not style shadowDOM
You need to load that CSS inside the shadowDOM because it arranges all the async loaded IMG tiles inside shadowDOM.
You also do not want to load the <script src=".../leaflet.js"> in your HTML.
It is a dependency of the Web Component. So the Web Component loads it when required
<style>
maps { display:grid; grid:90px 90px/1fr 1fr; gap:5px; background:green }
</style>
<maps>
<leaflet-map coords="52.09,5.12"></leaflet-map><leaflet-map coords="52.372,4.885"></leaflet-map>
<leaflet-map coords="51.924,4.47"></leaflet-map><leaflet-map coords="50.85,5.69"></leaflet-map>
</maps>
<script>
customElements.define('leaflet-map', class extends HTMLElement {
constructor() {
super().attachShadow({mode: 'open'}).innerHTML =
`<style>.leaflet-control-zoom{display:none}</style>` +
`<link rel="stylesheet" href="//cdn.skypack.dev/leaflet/dist/leaflet.css">` +
`<div style="height:100%"/>`;
}
connectedCallback() {
if (window.L) this.render();
else document.head.append(Object.assign(document.createElement("script"),{
src : "//unpkg.com/leaflet#latest/dist/leaflet.js",
onload : () => this.render() }))
}
render(){
this.map = L.map(this.shadowRoot.querySelector('div'));
this.map.addLayer(L.tileLayer("//tile.osm.org/{z}/{x}/{y}.png", { }));
this.setView( ...this.getAttribute("coords").split(",") );
}
setView( lat, lon, zoom=this.getAttribute("zoom")||13 ){
this.map.setView([lat, lon], zoom);
}
});
</script>
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.
I have a basic Node js express web application with webix UI on the front-end.
There are a lot of components/tables with a lot of data that needs to be populated in these tables.
I have a loading screen with some background-images changing background image URL every 3 secs until the loading of all the data is complete.
The problem with the loading screen is that the images are not the first thing that get downloaded by the browser. The tables sometimes populate even before the images get downloaded and it beats the whole purpose of having the loading screen.
I was wondering if there was a way I can load these images first thing when the application is opened in the browser.
Here is the HTML Code:
<body>
<div id="container-id" class="container">
<div id="text-id" class="text">Loading...</div>
</div>
</body>
Here is the CSS:
#-webkit-keyframes changeBg
{
0% {background-image: url("/resources/images/one.jpg");}
25% {background-image: url("/resources/images/two.jpg");}
50% {background-image: url("/resources/images/three.jpg");}
75% {background-image: url("/resources/images/four.jpg");}
100% {background-image: url("/resources/images/five.jpg");}
}
.container {
height: 100%;
width: 100%;
background-image: url("/resources/images/one.jpg");
-webkit-animation: changeBg 15s infinite;
background-size: 100%;
background-position: center;
}
Based on this similar question: load specific image before anything else
I tried loading the images using the javascript as first few lines in the index page as follows:
<head>
<script type="text/javascript">
(new Image()).src= window.location.href + "resources/images/one.jpg";
(new Image()).src= window.location.href + "resources/images/two.jpg";
(new Image()).src= window.location.href + "resources/images/three.jpg";
(new Image()).src= window.location.href + "resources/images/four.jpg";
(new Image()).src= window.location.href + "resources/images/five.jpg";
</script>
<link rel="stylesheet" href="webix.css" type="text/css" media="screen" charset="utf-8" />
<link rel="stylesheet" href="app.css" type="text/css" media="screen" charset="utf-8" />
</head>
But still, the other requests get served way before the images.
Actually scripts are loaded by order they are placed in the head. Browser won't wait for event to being raised then go for other scripts to load (It doesn't make sense). So your script is loaded completely but it's event for image loading are not raised.
So we have to await until all images are loaded then load scripts.
Here is the code:
<script type="text/javascript">
function loadImageAsync(url) {
return new Promise((resolve) => {
let img = new Image();
img.src = url;
img.addEventListener('load', e => resolve(img));
});
};
async function loadImages() {
var im1 = loadImageAsync("imageUrlOne");
var im2 = loadImageAsync("imageUrlTwo");
var image1 = await im1;
console.log(`image one loaded..., url:${image1.src}`)
var image2 = await im2
console.log(`image two loaded..., url:${image2.src}`)
}
function loadScript(url) {
var script = document.createElement("script")
script.type = "text/javascript";
script.src = url;
document.getElementsByTagName("head")[0].appendChild(script);
}
loadImages().then(() => {
console.log("loading scripts...");
loadScript("script 1 url here");
loadScript("script 2 url here");
});
</script>
You can use react js for such things, Reactjs makes lots of thing like this easy.
To know more about react .
Visit: https://reactjs.org/
import React from "react";
class ImageWithStatusText extends React.Component {
constructor(props) {
super(props);
this.state = { imageStatus: "loading" };
}
handleImageLoaded() {
this.setState({ imageStatus: "loaded" });
}
handleImageErrored() {
this.setState({ imageStatus: "failed to load" });
}
render() {
return (
<div>
<img
src={this.props.imageUrl}
onLoad={this.handleImageLoaded.bind(this)}
onError={this.handleImageErrored.bind(this)}
/>
{this.state.imageStatus}
</div>
);
}
}
export default ImageWithStatusText;
Note: I just vastly simplified the program and have left it with the bare essentials ...
So I am trying to add graphs within a sigma.js graph. First, let me post the javascript (sigmaAngualr.js) ...
app = angular.module('sigmaAngular', []);
app.controller('NetworkDataCtrl', function($scope){
var i, s;
$scope.newName = null;
$scope.s = null;
$scope.N = 3;
$scope.newNode = function (id, label){
return {id: id,
label: label,
x: Math.random(),
y: Math.random(),
size: Math.random()+0.2,
color:'#333'}
};
$scope.addNodeGraph = function(id, label){
$scope.s.graph.addNode($scope.newNode(id, label));
console.log(id+'-'+label);
};
$scope.tempNodes = [];
for( i=0; i<$scope.N; i++ )
$scope.tempNodes.push($scope.newNode('id'+i, 'Node'+i));
$scope.s = new sigma({graph:{nodes:$scope.tempNodes, edges:[]}})
});
app.directive('showGraph', function(){
// Create a link function
function linkFunction(scope, element, attrs){
scope.s.addRenderer({container: element[0]})
};
return {
scope: false,
link: linkFunction
}
});
And here is the HTML ...
<!DOCTYPE html>
<html>
<head>
<title>Using AngularJS</title>
<script type="text/javascript" src='lib/sigma/sigma.min.js'></script>
<script type="text/javascript" src='lib/angular/angular.min.js'></script>
<script type="text/javascript" src='lib/sigmaAngular.js'></script>
<link rel="stylesheet" type="text/css" href="main.css">
</head>
<body ng-app='sigmaAngular'>
<div>
<div ng-controller='NetworkDataCtrl'>
<input type='text' ng-model='newName' />
<button ng-click='addNodeGraph(newName,newName)'> Add Node </button>
<hr>
<div show-graph id='graph-container' s='s' tempNodes='tempNodes'>
</div>
<hr>
</div>
</div>
</body>
</html>
Of course, I am unable to add a node to the graph.
I had also tried another option of creating an entire graph when I add a node. That doesnt work either. (This is no longer true)
Let me try to explain. Now,
When I refresh the browser, I do not see a graph.
When I resize the browser window The graph suddenly appears.
When I type a random name and hit the Add Node button, the graph doesnt change.
When I resize the browser window, the graph changes to reveal the new node.
So in summary, I have to resize the browser window to see any changes in the graph.
Just a side note: The css is below:
#graph-container {
position: relative;
width: 200px;
height: 200px;
border: 1px solid indianred;
}
I feel now that I am really close to solving the problem. Just a tiny bit off somewhere ...
Any help will be greatly appreciated!!!
I'm trying to displayGoogle Maps on a html page in cordova.
The example code I'm trying to replicate is this one: https://developers.google.com/maps/documentation/javascript/examples/map-simple?hl=it
Here is my code:
HTML:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="format-detection" content="telephone=no" />
<meta name="msapplication-tap-highlight" content="no" />
<!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes. See https://issues.apache.org/jira/browse/CB-4323 -->
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width, height=device-height, target-densitydpi=device-dpi" />
<link rel="stylesheet" type="text/css" href="css/index.css" />
<title>Hello World</title>
</head>
<body>
<div class="app">
<div id="map-canvas"></div>
</div>
<script type="text/javascript" src="cordova.js"></script>
<script type="text/javascript" src=js/jquery-1.11.1.min.js></script>
<script src="https://maps.googleapis.com/maps/api/js?v=3.exp"></script>
<script type="text/javascript" src="js/google_map.js"></script>
<script type="text/javascript" src="js/index.js"></script>
</body>
index.js (I used some alert() and every method was called)
var app = {
initialize: function() {
this.bindEvents();
},
bindEvents: function() {
document.addEventListener('deviceready', this.onDeviceReady, false);
},
onDeviceReady: function() {
toIndex();
}
};
$(document).ready( function(){
app.initialize();
});
function toIndex(){
google_map.init();
}
google_map.js
the class which take care of the map
var google_map = (function(){
var _map;
var _$map_canvas;
function init(){
_$map_canvas = $('.app').find('#map-canvas');
var mapOptions = {
zoom: 8,
center: new google.maps.LatLng(-34.397, 150.644)
};
_map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
}
function getMap(){
return _map;
}
return {
init:init,
getMap:getMap
}
})();
and the index.css file
#map-canvas {
height: 100%;
margin: 0px;
padding: 0px
}
Instead of displaying the map, I see a white page.
Second question:
code here: http://jsfiddle.net/qbddfdk7/1/
This one doesn't work too, the problem is at line 14:
if I change it from
map = new google.maps.Map(map_canvas, mapOptions);
to
map = new google.maps.Map(document.getElementById('map-canvas'), mapOptions);
It will work, but I can't undesrstand why.
The problem with your second question is a timing issue.
You see, your inline javascript is in the <head>, so at the time var map_canvas = document.getElementById('map-canvas'); runs, the element with the id map-canvas is not loaded yet, so var map_canvas is set to null.
Moving the <script> tag to the bottom of the <body> element solves this issue. I've updated your jsFiddle accordingly.
As for your first issue, try Chrome Remote Debugging if available, or weinre. These will help you find the source of your problem.
I've had no luck using their recommendation to use percentages for the map-canvas. To ensure everything is working fine, hard-code the div tag to a fixed width and height.
Once you have the map displayed, then you'll need to write javascript to adjust the style width and height during the pagecreate or pageinit. You can then change the same properties during the orientationchange event.
There are several articles on the best way to find the screen size. I'm using:
function effectiveDeviceWidth(factor) {
var deviceWidth = window.orientation == 0 ? window.screen.width : window.screen.height;
// iOS returns available pixels, Android returns pixels / pixel ratio
// http://www.quirksmode.org/blog/archives/2012/07/more_about_devi.html
if (navigator.userAgent.indexOf('Android') >= 0 && window.devicePixelRatio) {
deviceWidth = deviceWidth / window.devicePixelRatio;
}
return parseInt(deviceWidth) * factor + 'px';
}
function effectiveDeviceHeight(factor) {
var deviceHeight = window.orientation == 0 ? window.screen.height : window.screen.width;
// iOS returns available pixels, Android returns pixels / pixel ratio
// http://www.quirksmode.org/blog/archives/2012/07/more_about_devi.html
if (navigator.userAgent.indexOf('Android') >= 0 && window.devicePixelRatio) {
deviceHeight = deviceHeight / window.devicePixelRatio;
}
return parseInt(deviceHeight) * factor + 'px';
}
$('#map-canvas').width(effectiveDeviceWidth(.8));
$('#map-canvas').height(effectiveDeviceHeight(.6));
Hope this helps.
The problem was in the css, in eclipse I corrcted the css to this:
html, body, #map-canvas {
height: 100%;
width: 100%;
position: relative;
z-index: -1;
margin: 0px;
padding: 0px
}
I'm new to web languages and I didn't know that I had to write css even for the hmtl and the body tag, as you can see those tag are missing from the code in the question.
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>'
);