How do you change the offset for a leaflet popup using angular leaflet directive and geojson? - leaflet

I'm using the angular-leaflet-directive and geojson to create map markers using leaflet and mapbox. The popups on the markers aren't correctly aligned on the marker.
angular.extend($scope, { // Map data
geojson: {
data: $scope.filteredShows,
onEachFeature: function (feature, layer) {
layer.bindPopup(feature.properties.artist + ' · ' + feature.properties.venue);
layer.setIcon(defaultMarker);
layer.on({
mouseover: pointMouseover,
mouseout: pointMouseout
});
layers[feature.properties.id] = layer;
}
}
});
How do I change the offset on the markers?

Using popupAnchor: [-10, -10], in L.Icon. See http://leafletjs.com/reference.html#icon

If you're using the default images, but they're placed at a different location with different filenames because you're using a Rails server to serve the assets, for example, here's a tip so you don't have to hard code in the values from the default icon.
In my case, I injected the actual values into the proper location.
<script type="text/javascript">
var injectedData = {
paths: {
leafletIcon: {
iconRetinaUrl: '<%= image_url "leaflet-1.3.4/marker-icon-2x.png" %>',
iconUrl: '<%= image_url "leaflet-1.3.4/marker-icon.png" %>',
shadowUrl: '<%= image_url "leaflet-1.3.4/marker-shadow.png" %>',
},
},
};
</script>
Then, I created an instance of Icon that uses the default values for image offsets directly from the Icon.Default prototype.
import { Icon } from 'leaflet';
const defaultIcon = new Icon({
...Icon.Default.prototype.options,
...injectedData.paths.leafletIcon,
});
That's the same as injecting your data directly. Do as is appropriate for your particular use case.
const defaultIcon = new Icon({
...Icon.Default.prototype.options,
{
iconRetinaUrl: "/assets/leaflet-1.3.4/marker-icon-2x-00179c4c1ee830d3a108412ae0d294f55776cfeb085c60129a39aa6fc4ae2528.png",
iconUrl: "/assets/leaflet-1.3.4/marker-icon-574c3a5cca85f4114085b6841596d62f00d7c892c7b03f28cbfa301deb1dc437.png",
shadowUrl: "/assets/leaflet-1.3.4/marker-shadow-264f5c640339f042dd729062cfc04c17f8ea0f29882b538e3848ed8f10edb4da.png",
},
});
In my case, I was using the react-leaflet library with React, not Angular, but I'm sure you can adapt your use-case appropriately. In my case, I used the defaultIcon as a prop for the Marker component.
<Map center={position} zoom={zoom}>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Marker icon={defaultIcon} position={position}>
<Popup>
<span>{this.props.location}</span>
</Popup>
</Marker>
</Map>
I know this doesn't answer your question directly, but your question and vitalik_74's answer got me on the road to what I needed for my particular use-case, which was an easy but reliable way to provide different image URLs for the default icon set (including altered filenames) while also reusing the default offset numbers without having to hard code them in. I hope my answer can help someone else who comes across this question with this issue in the future.

Related

Common tile layer settings for multiple vue2-leaflet maps

In my Vue (2.x) application, I have a number of places where I use vue2-leaflet maps.
There are a few common features of these maps, but they are otherwise completely different, so it doesn't make sense to have all of them use a single component that contains an <l-map> (and all other relevant map components).
In order to reduce copying an pasting, I have created a few mixins with common features. For example, I have written a mixin for any maps that need to be able to auto-fit to their contents, so the only thing I need to do in each component that uses it is add :bounds="autofitBounds" to the <l-map> component.
I wanted to do create something similar for the <l-tile-layer> component, because all of our maps use the same layers.
I created a mixin that provides the url and attribution for the tile layer like this:
export default {
data () {
return {
url: process.env.VUE_APP_MAP_TILE_URL || 'https://{s}.tile.osm.org/{z}/{x}/{y}.png',
attribution: process.env.VUE_APP_MAP_TILE_COPYRIGHT || '© OpenStreetMap contributors',
};
},
};
I can use it by adding the mixin to my component and then adding the <l-tile-layer> component inside the <l-map> like this:
<l-tile-layer :url="url" :attribution="attribution" />
I wanted to see if there was a way to reduce the boilerplate further.
First, I tried to create it as a component instead, like this:
<template>
<l-tile-layer :url="url" :attribution="attribution" />
</template>
<script>
import { LTileLayer } from 'vue2-leaflet';
export default {
name: 'MyTileLayer',
components: {
LTileLayer,
},
data () {
return {
url: process.env.VUE_APP_MAP_TILE_URL || 'https://{s}.tile.osm.org/{z}/{x}/{y}.png',
attribution: process.env.VUE_APP_MAP_TILE_COPYRIGHT || '© OpenStreetMap contributors',
};
},
};
</script>
Then I can use that component inside my <l-map> without needing to add any attributes to it, like this:
<my-tile-layer>
The problem is that this doesn't render properly. Here are two screenshots - the <l-tile-layer> used directly on the left, and the <my-tile-layer> wrapper on the right.
Is there a better way to create a tile layer component with default values? Or is there a way I can fix the rendering of <my-tile-layer>?

Leaflet map does not show up properly. Partially grey.

I have tried my best to take solutions here from diverse answers but my problem remains.
The map does not show up properly. A grey frame is taking almost 3/4 of the frame.
How the map shows up
<div id="map"></div>
<script>
var map = L.map('map',{scrollWheelZoom: false});
map.setView(<%= #location.latlng %>, 16);
marker = L.marker(<%= #location.latlng %>).addTo(map);
L.tileLayer('http://a.tile.osm.org/{z}/{x}/{y}.png', {
attribution: 'Your attribution statement',
maxZoom: 20,
subdomains: '',
}).addTo(map)
$(document).ready(function(){
L.Util.requestAnimFrame(map.invalidateSize,map,!1,map._container);
});
</script>
See Data-toggle tab does not download Leaflet map.
You probably need a longer delay before calling map.invalidateSize(). Ideally listen to the event that opens your map container to its correct size.

how to create a jsfiddle using leaflet

I am struggling with jsfiddle trying to create a running example which uses leaflet.
because I was not successful I searched for some examples and found the following one working:
http://jsfiddle.net/kedar2a/LnzN2/2/
I then copied the example in a new fiddle
https://jsfiddle.net/aLn3ut5z/1/
but it is still not working...
when inserting the external resources, there was the following error:
jsfiddle.net says:
You're loading resources over HTTP not HTTPS, your fiddle will not
work. Do you wish to continue?
any suggestions what is wrong here?
p.s.: below is the code of the jsfiddle windows:
HTML:
<div id="map"></div>
CSS:
#map {
height: 500px;
width: 80%;
}
JAVASCRIPT:
// We’ll add a tile layer to add to our map, in this case it’s a OSM tile layer.
// Creating a tile layer usually involves setting the URL template for the tile images
var osmUrl = 'http://{s}.tile.osm.org/{z}/{x}/{y}.png',
osmAttrib = '© OpenStreetMap contributors',
osm = L.tileLayer(osmUrl, {
maxZoom: 18,
attribution: osmAttrib
});
// initialize the map on the "map" div with a given center and zoom
var map = L.map('map').setView([19.04469, 72.9258], 12).addLayer(osm);
// Script for adding marker on map click
function onMapClick(e) {
var marker = L.marker(e.latlng, {
draggable: true,
title: "Resource location",
alt: "Resource Location",
riseOnHover: true
}).addTo(map)
.bindPopup(e.latlng.toString()).openPopup();
// Update marker on changing it's position
marker.on("dragend", function(ev) {
var chagedPos = ev.target.getLatLng();
this.bindPopup(chagedPos.toString()).openPopup();
});
}
map.on('click', onMapClick);
The Leaflet CDN doesn't support SSL yet. You can use something not requiring https, like playground-leaflet which is just a fork of JSBin with leaflet libraries easily selectable.
Alternatively, you could use Leaflet from cdnjs.net, which does support https.

Mapbox non-geographic

I'm trying to mixed up for at least a week , mapbox with leaflet to did a Non geographic map.
My first step was to build it with maptiler.com which generated with the tiled a code based on leaflet. But i want to add to this code a Geojson proprites.
I saw that in Mapbox there is already a geojson popup built-in.
This is why i want to use my leaflet map code + mapbox popup, it's possible ?
Thanks,
Jade
<!DOCTYPE html>
<html>
<head>
<title>map</title>
<meta charset="utf-8"/>
<meta name='viewport' content='initial-scale=1,maximum-scale=1,user-scalable=no' />
<script src='https://api.tiles.mapbox.com/mapbox.js/v2.1.5/mapbox.js'></script>
<script src="http://code.jquery.com/jquery-1.9.1.min.js"></script>
<script src="http://cdn.leafletjs.com/leaflet-0.6.4/leaflet.js"></script>
<link href='https://api.tiles.mapbox.com/mapbox.js/v2.1.5/mapbox.css' rel='stylesheet' />
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.css" />
<!--[if lte IE 8]>
<link rel="stylesheet" href="http://cdn.leafletjs.com/leaflet-0.7.3/leaflet.ie.css" />
<![endif]-->
<script>
L.mapbox.accessToken = 'pk.eyJ1IjoiamFkZTIyOTMiLCJhIjoiRDdweEFrZyJ9.Yk4XeNmp3SExkU41Z7BU3w';
function init() {
var mapMinZoom = 3;
var mapMaxZoom = 6;
var map = L.map('map', {
maxZoom: mapMaxZoom,
minZoom: mapMinZoom,
crs: L.CRS.Simple
}).setView([0, 0], mapMaxZoom);
var mapBounds = new L.LatLngBounds(
map.unproject([0, 7680], mapMaxZoom),
map.unproject([10496, 0], mapMaxZoom));
map.fitBounds(mapBounds);
L.tileLayer('{z}/{x}/{y}.png', {
minZoom: mapMinZoom, maxZoom: mapMaxZoom,
bounds: mapBounds,
noWrap: true
}).addTo(map);
// The GeoJSON representing a point feature with a property of 'video' for the Vimeo iframe
var geoJson = {
features: [{
type: 'Feature',
properties: {
'marker-color': '#f00',
'marker-size': 'large',
'marker-symbol': 'rocket',
video: '<iframe src="//player.vimeo.com/video/106112939" width="380" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> <p><h2>How Simplicity Will Save GIS</h2><p>Vladimir Agafonkin from FOSS4G on Vimeo.</p>',
},
geometry: {
type: 'Point',
coordinates: [0,0]
}
}]
};
var myLayer = L.mapbox.featureLayer().addTo(map);
// Add the iframe in a marker tooltip using the custom feature properties
myLayer.on('layeradd', function(e) {
var marker = e.layer,
feature = marker.feature;
// Create custom popup content from the GeoJSON property 'video'
var popupContent = feature.properties.video;
// bind the popup to the marker http://leafletjs.com/reference.html#popup
marker.bindPopup(popupContent,{
closeButton: false,
minWidth: 320
});
});
// Add features to the map
myLayer.setGeoJSON(geoJson);
}
</script>
<style>
html, body, #map { width:100%; height:100%; margin:0; padding:0; }
background-color:white;
</style>
</head>
<body onload="init()">
<div id="map"></div>
</body>
</html>
It seems that you're asking 2 separate questions here. The original question about non-geographic maps and your follow-up question about adding an iframe to a leaflet popup. I'll try to address your follow-up question:
Let's take the Mapbox example you linked (https://www.mapbox.com/mapbox.js/example/v1.0.0/video/) and adapt it to work with the video you would like to display.
If you've already got some GeoJSON data, you can edit it to include a video property. Let's look at the GeoJSON code from the Mapbox example:
var geoJson = {
features: [{
type: 'Feature',
properties: {
'marker-color': '#f00',
'marker-size': 'large',
'marker-symbol': 'rocket',
video: '<iframe src="//player.vimeo.com/video/106112939" width="380" height="281" frameborder="0" webkitallowfullscreen mozallowfullscreen allowfullscreen></iframe> <p><h2>How Simplicity Will Save GIS</h2><p>Vladimir Agafonkin from FOSS4G on Vimeo.</p>',
},
geometry: {
type: 'Point',
coordinates: [0,0]
}
}]
};
See that video property? Its value contains the iframe code that will end up inside the popup for the map marker it corresponds to. I went ahead and added the iframe code from your YouTube video to the above example and you can see it in action on jsfiddle here: http://jsfiddle.net/danswick/tcxvpw84/.
Your GeoJSON data probably doesn't have a video property, but you can add it using a text editor or geojson.io.
Further down in our example code, we access that video property, set it to a variable, and bind it to our marker's popup:
// Create custom popup content from the GeoJSON property 'video'
var popupContent = feature.properties.video;
// bind the popup to the marker http://leafletjs.com/reference.html#popup
marker.bindPopup(popupContent,{
closeButton: false,
minWidth: 320
});
Mapbox just uses Leaflet's bindPopup method which comes standard with L.Marker. If you create a L.GeoJSON layer, you can add a popup to each feature using the onEachFeature option of L.GeoJSON which takes a function with two parameters: feature and layer. In there you can bind a popup to your feature:
For example when you have features like this one, with a property called name:
{
"type": "Feature",
"properties": {
"name": "E"
},
"geometry": {
"type": "Point",
"coordinates": [0, 0]
}
}
You could then use that name value when binding a popup to your feature like this:
// Create new GeoJSON layer
L.geoJson(data, {
// Define the onEachFeature function which runs on every feature
onEachFeature: function (feature, layer) {
// Bind a popup to the layer using the name property
layer.bindPopup(feature.properties.name);
}
}).addTo(map);
Here's a working example on Plunker: http://plnkr.co/edit/iPLHqi?p=preview
Thanks, to take time to reply.
But actually i wanted to use geojson just to put iframe in a leaflet popup.
like this :
L.marker(map.unproject([452, 410])).addTo(map).bindPopup("<iframe width="560" height="315" src="https://www.youtube.com/embed/zP71_cXfiu0" frameborder="0" allowfullscreen></iframe>");
But it doesn't work but with the same syntax this work : I just saw in this exemple that with geojson it's might work :
https://www.mapbox.com/mapbox.js/example/v1.0.0/video/
L.marker(map.unproject([452, 410])).addTo(map).bindPopup("https://www.youtube.com/embed/zP71_cXfiu0");
Sorry if i'm a little bit confusing, because i'm designer and all this "code thing" it's new for me :)

loading leaflet map in ajax loaded page renders no tiles?

Hi I´m doing a phonegap app that loads my subpages with ajax and in one off them I´m trying to load a leaflet map.
It is not rendering the tiles?
I don´t know what I´m missing?
I load the leaflet css and js file in my index file and in my subpage that should display the map I have the following code:
<div id="themappage">
<div id="header" class="toolbar">
<h1>The Map</h1>
BACK
</div>
<div id="map"></div>
<script>
$(document).ready(function(){
var map = L.map('map');
L.tileLayer('http://{s}.tile.cloudmade.com/42dfb943872a465d89807eb88f6a1f4d/997#2x/256/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, CC-BY-SA, Imagery © CloudMade'
}).addTo(map);
function onLocationFound(e) {
var radius = e.accuracy / 2;
L.marker(e.latlng).addTo(map)
.bindPopup("You are within " + radius + " meters from this point").openPopup();
L.circle(e.latlng, radius).addTo(map);
}
function onLocationError(e) {
alert(e.message);
}
map.on('locationfound', onLocationFound);
map.on('locationerror', onLocationError);
map.locate({setView: true, maxZoom: 16});
});
</script>
</div>
Any input appreciated, thanks.
Update!
Just found out if I use http://{s}.tile.osm.org/{z}/{x}/{y}.png instead off http://{s}.tile.cloudmade.com/42dfb943872a465d89807eb88f6a1f4d/997/256/{z}/{x}/{y}.png as the tile layer then it renders the tiles, why doesn´t the tiles from cloudemade render?
When I tried to load it, I got a 403 (Forbidden) error using that API-Key. Try to get another one (you did request your own right?). The reason that the OSM works is that it doesn't require an API-Key.
The API-Key is the part of the url that starts with 42dfb... and goes to the /. Replace that with a good key and you should be good to go.