I have a Leaflet map with a polyline and a whole bunch of icons. When I pan the map along the line, the icons are always visible but the line disappears. This happens while the map is being dragged (if I am panning slowly), or while the map is moving (if I quickly grab the map and release with a flicking motion). The polyline behavior is the same with or without { renderer: L.canvas() }.
Why does this happen and how can I make the line be visible while panning?
var mymap = L.map('mapid').setView([51.505, -0.09], 10);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(mymap);
var array = [];
for (var counter = 0; counter < 100; counter++) {
array[counter] = [51.505 - counter * 0.1, -0.09 - counter * 0.1]
}
L.polyline(array, {
renderer: L.canvas()
}).addTo(mymap);
for (var index = 0; index < array.length; index++) {
var latlng = array[index];
L.marker(latlng).addTo(mymap);
}
#mapid {
height: 90vh;
}
<script src="https://unpkg.com/leaflet#1.0.3/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.0.3/dist/leaflet.css" rel="stylesheet" />
<div id="mapid"></div>
And the same demo, as a JSFiddle: https://jsfiddle.net/xbxrtx50/1/
Why does this happen?
Performance.
Drawing things with SVG or Canvas is computationally expensive, and blocks the UI thread, so Leaflet does it as few times as possible. This means redrawing only when there is no zoom or pan interaction going on.
and how can I make the line be visible while panning?
Instantiate your L.Renderer manually (either L.SVG or L.Canvas), and use its padding option to make it much larger than the visible size of the map. That will alleviate the rendering artifacts.
You might also want to try out Leaflet.VectorGrid.Slicer, which should render the geometries in a tiled manner while the map is being panned around.
Related
I'm using mapboxgl and I'm also using ThreeJS be able to import 3D model to the scene. The 3D model that I used has very high polygon count. Due to MapboxGl's render function triggering in each frame my browser is being very laggy. Is it possible to trigger the render function only once or which function must use at this point istead of render function ? I would like to render my 3D model only once on the map.
Here is my codes:
mapBoxGLSetup: function () {
mapboxgl.accessToken = "";
oOriginPoint = [29.400261610397465, 40.87692013157027, 1];
oMap = new mapboxgl.Map({
logoPosition: "bottom-right",
container: oSceneContainer.id,
style: 'mapbox://styles/mapbox/streets-v11',
center: oOriginPoint,
zoom: 15,
pitch: 0,
antialias: true
});
var modelOrigin = oOriginPoint;
var modelAltitude = 0;
var modelRotate = [Math.PI / 2, Math.PI / 6.5, 0];
var modelAsMercatorCoordinate = mapboxgl.MercatorCoordinate.fromLngLat(
modelOrigin,
modelAltitude
);
o3DModelTransform = {
translateX: modelAsMercatorCoordinate.x,
translateY: modelAsMercatorCoordinate.y,
translateZ: modelAsMercatorCoordinate.z,
rotateX: modelRotate[0],
rotateY: modelRotate[1],
rotateZ: modelRotate[2],
scale: (modelAsMercatorCoordinate.meterInMercatorCoordinateUnits() / 1000) * 0.85
};
},
oSceneMapSetup: function () {
oMap.on('style.load', function () {
oMap.addLayer({
id: 'custom_layer',
type: 'custom',
renderingMode: '3d',
onAdd: function (oMapElement, oGlElement) {
base.oMapElement = oMapElement;
base.setupRenderer(oMapElement, oGlElement);
base.setupLayout(); // I'm loading 3D model in this function
base.setupRayCaster();
},
render: function (gl, matrix) {
// This render function is triggering each frame
var rotationX = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(1, 0, 0), o3DModelTransform.rotateX);
var rotationY = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 1, 0), o3DModelTransform.rotateY);
var rotationZ = new THREE.Matrix4().makeRotationAxis(new THREE.Vector3(0, 0, 1), o3DModelTransform.rotateZ);
var oMatrix = new THREE.Matrix4().fromArray(matrix);
var oTranslation = new THREE.Matrix4().makeTranslation(o3DModelTransform.translateX, o3DModelTransform.translateY, o3DModelTransform.translateZ)
.scale(new THREE.Vector3(o3DModelTransform.scale, -o3DModelTransform.scale, o3DModelTransform.scale))
.multiply(rotationX)
.multiply(rotationY)
.multiply(rotationZ);
oCamera.projectionMatrix = oMatrix.multiply(oTranslation);
oRenderer.resetState();
oRenderer.render(oScene, oCamera);
base.oMapElement.triggerRepaint();
}
})
});
},
Thanks for your help and support.
As long as you still calling triggerRepaint on each layer render loop, you will repaint the full map, it’s inherent to the way CustomLayerInterface and update layer work in Mapbox.
When I did my first research on the TriggerRepaint topic, I found a quite old issue in Mapbox where a guy tested all the different options, including having a fully separated context and even 2 mapbox instances, one of them empty. Here is the link
The performance was obviously better in terms of FPS/memory, but there were other collaterals that I personally wouldn't assume for threebox, like losing the depth calculation between mapbox fill-extrusions and 3D custom layer.
Sharing context
Different contexts & canvas
The second issue is the delay between the movement of both cameras. While current sharing context ensures the objects are fixed and stuck to a coords set, creating different contexts will produce a soft dragging effect where the delay between the 2 contexts render can be visually perceived when the map moves first and the 3D objects follow. It's perceivable even with ne single cube, so with thousands of objects will be definitely clearer.
Yellow is the image, cyan is the coords, red is the centre for both. Coords are like that because that is the allowable area in the map.
Ive got coordinates where the actual center of them is not the middle point of the coordinates. That means each side is a little wonky from the center.
As i know the exact center from the coordinates, could it be possible to anchor that center to the middle of the image where it is the actual center for those coordinates. Would be a blast to just slap it there.
Or could i somehow add a margin towards each direction so with a little fiddling i could make it like in the illustration.
I have attempted to play with the coordinates themselves to make it fit the image, but that feels impossible to make it right, the image either goes wonky or the markers that i add go off their right spots whenever i get one of the edges right.
Edit: and im using CRS.Simple here.
The best solution is using the turf library
const map = L.map('mapid').setView([52.308478623663355, 19.281005859375004], 5);
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
maxZoom: 18,
attribution: '© OpenStreetMap contributors'
}).addTo(map);
const test = [
[15.3369140625, 49.55372551347579],
[23.97216796875, 49.55372551347579],
[23.97216796875, 54.316523240258256],
[15.3369140625, 54.316523240258256],
[15.3369140625, 49.55372551347579]
];
const polygon = turf.polygon([test]);
const centroid = turf.centroid(polygon);
const featCollection = {
features: [polygon, centroid]
}
const polu = L.geoJSON(featCollection).addTo(map);
map.fitBounds(poly.getBounds());
* {
margin: 0;
padding: 0;
}
html,
body {
height: 100%;
}
body {
min-height: 100%;
}
#mapid {
width: 100%;
height: 100%;
}
<link rel="stylesheet" href="https://unpkg.com/leaflet#1.6.0/dist/leaflet.css" />
<script src="https://unpkg.com/leaflet#1.6.0/dist/leaflet.js"></script>
<script src="https://cdn.jsdelivr.net/npm/#turf/turf#5/turf.min.js"></script>
<div id="mapid"></div>
I don't recommand to use turf if it is not necessary.
You can use the Leaflet built-in getCenter() function.
getCenter() is working for Polyline, Polygon and Rectangles.
getCenter(): Returns the center (centroid) of the polyline / polygon / rectangle.
Resolved by making calibration points to the imageOverlay, taking coords from those points, adding them into leaflet as markers, then fiddling around once again but with those calibration points i got them centered in under an hour. Still not perfect but i guess im happy with it.
I have a map with 20 markers on a geojson layer. They are all from the same SVG and represent a certain area on the map (meaning they represent a circle of constant radius in meter on the map). I need these marker to adapt their size according to zoom level.
I've tried to use a circle as a marker. But my marker needs to be a SVG because it is complex graphic and radius doesn't apply to markers.
Here's the piece of code used to display the markers. Latitude, Longitude and rotationAngle are parsed from a .csv sheet:
for (i in chapters) {
var c = chapters[i];
if (!isNaN(parseFloat(c['Latitude'])) && !isNaN(parseFloat(c['Longitude']))) {
var lat = parseFloat(c['Latitude']);
var lon = parseFloat(c['Longitude']);
var cercleDirection = parseFloat(c['Direction']);
var photoIcon = L.icon({
iconUrl: 'media/Cercle.svg',
iconSize: [220, 220],
iconAnchor: [110, 110],
});
markers.push(
L.marker([lat, lon], {
icon: photoIcon,
rotationAngle: cercleDirection
}));
}
I have a Leaflet map with a polyline and a whole bunch of icons. When I pan the map along the line, the icons are always visible but the line disappears. This happens while the map is being dragged (if I am panning slowly), or while the map is moving (if I quickly grab the map and release with a flicking motion). The polyline behavior is the same with or without { renderer: L.canvas() }.
Why does this happen and how can I make the line be visible while panning?
var mymap = L.map('mapid').setView([51.505, -0.09], 10);
L.tileLayer('https://api.tiles.mapbox.com/v4/{id}/{z}/{x}/{y}.png?access_token=pk.eyJ1IjoibWFwYm94IiwiYSI6ImNpandmbXliNDBjZWd2M2x6bDk3c2ZtOTkifQ._QA7i5Mpkd_m30IGElHziw', {
maxZoom: 18,
attribution: 'Map data © OpenStreetMap contributors, ' +
'CC-BY-SA, ' +
'Imagery © Mapbox',
id: 'mapbox.streets'
}).addTo(mymap);
var array = [];
for (var counter = 0; counter < 100; counter++) {
array[counter] = [51.505 - counter * 0.1, -0.09 - counter * 0.1]
}
L.polyline(array, {
renderer: L.canvas()
}).addTo(mymap);
for (var index = 0; index < array.length; index++) {
var latlng = array[index];
L.marker(latlng).addTo(mymap);
}
#mapid {
height: 90vh;
}
<script src="https://unpkg.com/leaflet#1.0.3/dist/leaflet.js"></script>
<link href="https://unpkg.com/leaflet#1.0.3/dist/leaflet.css" rel="stylesheet" />
<div id="mapid"></div>
And the same demo, as a JSFiddle: https://jsfiddle.net/xbxrtx50/1/
Why does this happen?
Performance.
Drawing things with SVG or Canvas is computationally expensive, and blocks the UI thread, so Leaflet does it as few times as possible. This means redrawing only when there is no zoom or pan interaction going on.
and how can I make the line be visible while panning?
Instantiate your L.Renderer manually (either L.SVG or L.Canvas), and use its padding option to make it much larger than the visible size of the map. That will alleviate the rendering artifacts.
You might also want to try out Leaflet.VectorGrid.Slicer, which should render the geometries in a tiled manner while the map is being panned around.
I have a geojson polygon adding to the map with the click of a button. I also have the style of the polygon changing on the mousedown event on the geojson and the x/y coord pairs (the geojson geometry) printing to the console accessing it through the queryRenderedFeatures call on the API.
I am now wanting to make the polygon draggable like the point example (links below) on the mousedown event on the polygon and be able to move it on the map, updating the x/y coords of the polygon nodes throughout the mousedown event, but keeping the geojson size intact throughout the drag.
Is straight mapbox-gl-js the way to do this, or should I be feeding a pre-configured geojson polygon into a mapbox-gl-draw - draw polygon mode on a user's action?
Any suggestions or examples?
API Drag A Point Example
Drag A Point GitHub Code
Try this
var isDragging = false;
var startCoords;
map.on('click', function(e) {
var features = map.queryRenderedFeatures(e.point, { layers: ['polygon-layer'] });
var polygon = features[0];
if (!polygon) return;
startCoords = polygon.geometry.coordinates[0];
});
map.on('mousedown', function(e) {
isDragging = true;
});
map.on('mousemove', function(e) {
if (!isDragging) return;
var coords = map.unproject(e.point);
var delta = {
lng: coords.lng - startCoords[0],
lat: coords.lat - startCoords[1]
};
polygon.geometry.coordinates[0] = polygon.geometry.coordinates[0].map(function(coord) {
return [coord[0] + delta.lng, coord[1] + delta.lat];
});
map.getSource('polygon-source').setData(polygon);
});
map.on('mouseup', function(e) {
isDragging = false;
});
the polygon is being stored as a GeoJSON feature, and the polygon layer and source are named 'polygon-layer' and 'polygon-source', respectively. You will need to adjust these names to match your setup.