I use leafletjs to make a interactive map. I have a fixed list of URLs to use and I can not change the URLs.
I want to make a map with those tiles always in the same place and in the place I want them to be.
Tile0 is in (0, 0)
Tile1 is in (0, 1)
and so on. How do I decide where each tile goes?
var i = 0;
L.TileLayer.CustomUrlLayer = L.TileLayer.extend({
getTileUrl: function(coords) {
var x = i;
var y = i;
i = i + 1;
return `https://example.com/${y}-${x}.png`;
}
});
L.tileLayer.myLayer = function(templateUrl, options) {
return new L.TileLayer.MyLayer(templateUrl, options);
}
var map = L.map('map').setView([0, 0], 18);
L.tileLayer.myLayer('', {
attribution: 'My Map',
maxZoom: 18,
noWrap: true,
tileSize: 256,
}).addTo(map);
This snippet show my current leafletjs map. The tiles are placed wherever and it depends on the screen size. It changes if my Google Chrome window is smaller and so on. The correct images are loaded.
Tile0 Tile1 Tile2 Tile3
Tile4 Tile5 Tile6 Tile7
...
always regardless of the screen. How?
Related
I tried adding a gridlayer, but that obviously scales the grid lines with the tiles rather than being fixed to the coordinate system. I want to add a simple grid of lines based on the coordinate system so it is independent from the zoom levels to show the individual coordinates. Ideally I would want to show the coordinate as well and only present them at lower zoom levels.
Could I just add this to the tiles as overlays? How can I set a zoom level cutoff for that?
Code I already have:
var TileLayer = L.TileLayer.extend({
getTileUrl: function (coords) {
var data = {
r: L.Browser.retina ? '#2x' : '',
s: this._getSubdomain(coords),
z: this._getZoomForUrl()
};
var tilepoints = Math.pow(2, data['z'] - 1);
data['x'] = tilepoints * coords.x;
data['y'] = tilepoints * (Math.abs(coords.y) - 1);
return L.Util.template(this._url, L.extend(data, this.options));
}
});
var tiles = new TileLayer(baseurl + "/{z}-{x}-{y}.png", {
crs: L.CRS.Simple,
minZoom: MIN_ZOOM_LEVEL,
maxZoom: MAX_ZOOM_LEVEL,
zoomOffset: 1,
zoomReverse: true,
bounds: [[0, 0], [1048576, 1048576]],
});
var map = L.map(mapDiv, {
crs: L.CRS.Simple,
minZoom: MIN_ZOOM_LEVEL,
maxZoom: MAX_ZOOM_LEVEL,
maxBounds: [[0, 0], [1048576, 1048576]],
layers: [tiles]
});
The markers will appear on the map if I pan the map, but if I zoom out and then zoom in on a copy of the map without the markers, they will not appear until I pan again.
Is it possible to change this so that zooming in and out will cause the markers to recalculate on the map?
L.map('map', {
'center': [0, 0],
'zoom': 0,
'worldCopyJump': true,
'layers': [
L.tileLayer('http://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © OpenStreetMap contributors'
}),
L.marker([0, -135]),
L.marker([0, -90]),
L.marker([0, -45]),
L.marker([0, 0]),
L.marker([0, 45]),
L.marker([0, 90]),
L.marker([0, 135])
]
});
body {
margin: 0;
}
html, body, #map {
height: 100%
}
<link href="https://unpkg.com/leaflet/dist/leaflet.css" rel="stylesheet"/>
<script src="https://unpkg.com/leaflet/dist/leaflet-src.js"></script>
<div id="map"></div>
http://embed.plnkr.co/mWKc4M/
The markers will appear on the map if I pan the map
Specifically, this behaviour happens only when dragging the map (and not when panning the map via any other method, e.g. using keyboard shortcuts). This is because, internally, the worldCopyJump functionality is defined inside the Drag handler at src/map/handler/Map.Drag.js:
// TODO refactor, move to CRS
// #option worldCopyJump: Boolean = false
// With this option enabled, the map tracks when you pan to another "copy"
// of the world and seamlessly jumps to the original one so that all overlays
// like markers and vector layers are still visible.
worldCopyJump: false,
(Do note that Leaflet has an explanation of what map handlers are and how they work)
As the code stands now, the worldCopyJump functionality affects only the dragging handler, and works by resetting the drag offset (instead of the map center) every time the map dragging handler is about to be updated:
if (map.options.worldCopyJump) {
this._draggable.on('predrag', this._onPreDragWrap, this);
map.on('zoomend', this._onZoomEnd, this);
map.whenReady(this._onZoomEnd, this);
}
/* snip */
_onPreDragWrap: function () {
// TODO refactor to be able to adjust map pane position after zoom
var worldWidth = this._worldWidth,
halfWidth = Math.round(worldWidth / 2),
dx = this._initialWorldOffset,
x = this._draggable._newPos.x,
newX1 = (x - halfWidth + dx) % worldWidth + halfWidth - dx,
newX2 = (x + halfWidth + dx) % worldWidth - halfWidth - dx,
newX = Math.abs(newX1 + dx) < Math.abs(newX2 + dx) ? newX1 : newX2;
this._draggable._absPos = this._draggable._newPos.clone();
this._draggable._newPos.x = newX;
},
So, what to do? An option is to leverage wrapLatLng to reset the map center on every zoomend event, e.g.:
map.on('zoomend', function(ev){
map.panTo(map.wrapLatLng(map.getCenter()), {animate: false})
});
That should just work. See a working demo.
As an alternative, consider using https://gitlab.com/IvanSanchez/Leaflet.RepeatedMarkers , which will create copies of each marker every 360° of longitude.
I am trying to display a color bar (it is a legend for my map layers). I am doing this by combining d3 and leaflet. Here is my current implementation.
var map = L.map('map', {
center: [19.2436590371, 96.8861699425],
zoom: 9,
minZoom: 4,
maxZoom: 9,
layers: [osm, lyr1] // These are some generated layers
});
// Add tile layers for my map ....
let svg = d3.select(map.getPanes().overlayPane).append('svg').attr('id', 'svg');
length = 100;
color = d3.scaleLinear().domain([1, length])
.interpolate(d3.interpolateHcl)
.range([d3.rgb("#007AFF"), d3.rgb('#FFF500')]);
for (var i = 0; i < length; i++) {
svg.append('div').attr('style', function (d) {
return 'background-color: ' + color(i);
});
}
The problem is that nothing get displayed on top of leaflet maps. Has anybody a solution for this ? Thanks for you help.
I am displaying an ImageMapType as overlay over a satellite mapType base map.
The base map seems to be limiting the max zoom to 19 in my area, if I change to a Road mapType I get a a couple of more levels. I have high resolution imaginery which I am displaying in the overlay and I want to be able to zoom further. If I use the ImageMapType as base map I can zoom in all I need, but I would really like to display the satellite base map and then continue to zoom into the image even if there's no satellite imaginery available.
Is there any way of accomplishing this? The only thing I can think of is creating a custom base map.
Code is similar to this example https://developers.google.com/maps/documentation/javascript/examples/maptype-image-overlay
Thanks!
My code:
var mapMinZoom = 10;
var mapMaxZoom = 25;
var zoom = 20;
function initialize() {
map = new google.maps.Map(document.getElementById('map-canvas'), {
center: new google.maps.LatLng(-34.567426059726316, -60.34467143006623),
zoom: zoom,
mapTypeId: google.maps.MapTypeId.SATELLITE,
panControl: false,
streetViewControl: false,
maxZoom: mapMaxZoom
});
createImageLayer('imaginery', 'images')
}
function createImageLayer(key, folder) {
var mapBounds = new google.maps.LatLngBounds(new google.maps.LatLng(-34.55771388565057, -60.367219001054764), new google.maps.LatLng(-34.55817334541287, -60.3209562599659));
var imageMapTypeLayer = new google.maps.ImageMapType({
minZoom: mapMinZoom,
maxZoom: mapMaxZoom,
name: key,
getTileUrl: function(coord, zoom) {
if ((zoom < mapMinZoom) || (zoom > mapMaxZoom)) {
return "http://www.maptiler.org/img/none.png";
}
var ymax = 1 << zoom;
var y = ymax - coord.y -1;
var tileBounds = new google.maps.LatLngBounds(
map.getProjection().fromPointToLatLng( new google.maps.Point( (coord.x), (coord.y) ) ),
map.getProjection().fromPointToLatLng( new google.maps.Point( (coord.x), (coord.y)))
);
// write a real condition here, so long this is always valid
if (tileBounds.intersects(tileBounds)) {
return "/static/ui/"+ folder + "/"+zoom+"/"+coord.x+"/"+y+".png";
} else {
return "http://www.maptiler.org/img/none.png";
}
},
tileSize: new google.maps.Size(256, 256)
});
map.overlayMapTypes.push(imageMapTypeLayer)
}
EDIT 1:
Up to now I have managed to overcome this by listening to the zoom_changed event and changing the base map to my imageMapType when the zoom reaches the maximum and back to satellite when the user zooms out. I wonder if there's a cleaner solution to this issue.
I have a map I exported from tilemill, made a mapbox map and threw some points on it. The view starts off looking at the US, with a marker somewhere in the middle. If I pan left until the next time I see the US, the markers are gone. Here's the code minus the geoJson data.
var map = L.mapbox.map('map', 'natecraft1.xdan61or').setView([-102, 39], 4);
map.markerLayer.on('layeradd', function(e) {
var marker = e.layer;
var feature = marker.feature;
var image = feature.properties.images
// var img = images
// Create custom popup content
var popupContent = '<img class="pics" src="' + image + '" />'
marker.bindPopup(popupContent,{
closeButton: false,
minWidth: 320,
offset: [180, 20]
});
});
map.markerLayer.setGeoJSON(geoJson);
map.markerLayer.on('click', function(e) {
e.layer.openPopup();
var lat = e.layer.getLatLng().lat;
var lng = e.layer.getLatLng().lng;
map.panTo([lat+5, lng+5], 2);
});
map.markerLayer.on('', function(e) {
e.layer.closePopup();
});
Your tilelayer is wrapping around the globe for coordinates outside of the (-180, 180) range. Best option is to set the maxBounds option so users don't pan outside of that map and just get bounced back.
var map = L.mapbox.map('map', 'examples.map-9ijuk24y').setView([0,0], 2);
var layer = L.mapbox.tileLayer('examples.map-9ijuk24y', {
noWrap: true,
minZoom: 3
}).addTo(map);
map.options.maxBounds = map.getBounds();
Here's a live demo of what that would look like