Why does my Leaflet circle only show as a dot? - leaflet

I'm trying to add circles to my map, but for some reason the circles only show as dots, irrespective of the radius size.
var circle = L.circle(map.unproject([9541, 7658], map.getMaxZoom()), {
radius: 500
}).addTo(map);
I'm using pixel coordinates, but as you can see I'm converting them, so even though I only get dots on the map, they show at the right coordinates. I would hope this isn't the issue, but...?
I've successfully added circleMarkers, but the radius doesn't grow when zooming. At least not that I could see.
So the question is: how can I get the dots to show as circles?
Using Leaflet 1.9.3
Update
It appears that with pixel coordinates you need to enter a really high value for the radius. Thought I had already tried this before asking the question but apparently not.
var circle = L.circle(map.unproject([9541, 7658], map.getMaxZoom()), {
radius: 50000
}).addTo(map);
Unfortunately they're all showing at different sizes, even with the same radius, but that's a different question...

I originally misread your question and gave an incorrect answer, sorry. Circle radius is static. I think that the best way to change it with zoom level would be to use a zoom event listener:
let currZoom = map.getZoom();
let circles = [/* Store your circles here as you create them */];
map.on("zoomend", () => {
const zoomDiff = map.getZoom() - currZoom;
currZoom = map.getZoom();
for (const circle of circles) {
circle.setRadius(circle.getRadius() * 2 ** zoomDiff);
}
});
It's been a while since I've worked with Leaflet, but I think that will do the trick.
Edited to account for your comment regarding multiple circles.

Related

Leaflet.js (or other solution) zoom to magnified pixels without blur

I've been using Leaflet to display raster images lately.
What I would like to do for a particular project is be able to zoom in to an image so the pixels become magnified on the screen in a sharply delineated way, such as you would get when zooming in to an image in Photoshop or the like. I would also like to retain, at some zoom level before maximum, a 1:1 correspondence between image pixel and screen pixel.
I tried going beyond maxNativeZoom as described here and here, which works but the interpolation results in pixel blurring.
I thought of an alternative which is to make the source image much larger using 'nearest neighbour' interpolation to expand each pixel into a larger square: when zoomed to maxNativeZoom the squares then look like sharply magnified pixels even though they aren't.
Problems with this are:
image size and tile count get out of hand quickly (original image is 4096 x 4096)
you never get the 'pop' of a 1:1 correspondence between image pixel and screen pixel
I have thought about using two tile sets: the first from the original image up to it's maxNativeZoom, and then the larger 'nearest neighbour' interpolated image past that, following something like this.
But, this is more complex, doesn't avoid the problem of large tile count, and just seems inelegant.
So:
Can Leaflet do what I need it to and if so how?
If not can you point me in the right direction to something that can (for example, it would be interesting to know how this is achieved)?
Many thanks
One approach is to leverage the image-rendering CSS property. This can hint the browser to use nearest-neighbour interpolation on <img> elements, such as Leaflet map tiles.
e.g.:
img.leaflet-tile {
image-rendering: pixelated;
}
See a working demo. Beware of incomplete browser support.
A more complicated approach (but one that works across more browsers) is to leverage WebGL; in particular Leaflet.TileLayer.GL.
This involves some internal changes to Leaflet.TileLayer.GL to support a per-tile uniform, most critically setting the uniform value to the tile coordinate in each tile render...
gl.uniform3f(this._uTileCoordsPosition, coords.x, coords.y, coords.z);
...having a L.TileLayer that "displays" a non-overzoomed tile for overzoomed tile coordinates (instead of just skipping the non-existent tiles)...
var hackishTilelayer = new L.TileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
'attribution': 'Map data © OpenStreetMap contributors',
maxNonPixelatedZoom: 3
});
hackishTilelayer.getTileUrl = function(coords) {
if (coords.z > this.options.maxNonPixelatedZoom) {
return this.getTileUrl({
x: Math.floor(coords.x / 2),
y: Math.floor(coords.y / 2),
z: coords.z - 1
});
}
// Skip L.TileLayer.prototype.getTileUrl.call(this, coords), instead
// apply the URL template directly to avoid maxNativeZoom shenanigans
var data = {
r: L.Browser.retina ? '#2x' : '',
s: this._getSubdomain(coords),
x: coords.x,
y: coords.y,
z: coords.z // *not* this._getZoomForUrl() !
};
var url = L.Util.template(this._url, L.Util.extend(data, this.options));
return url;
}
... plus a fragment shader that rounds down texel coordinates prior to texel fetches (plus a tile-coordinate-modulo-dependant offset), to actually perform the nearest-neighbour oversampling...
var fragmentShader = `
highp float factor = max(1., pow(2., uTileCoords.z - uPixelatedZoomLevel));
vec2 subtileOffset = mod(uTileCoords.xy, factor);
void main(void) {
vec2 texelCoord = floor(vTextureCoords.st * uTileSize / factor ) / uTileSize;
texelCoord.xy += subtileOffset / factor;
vec4 texelColour = texture2D(uTexture0, texelCoord);
// This would output the image colours "as is"
gl_FragColor = texelColour;
}
`;
...all tied together in an instance of L.TileLayer.GL (which syncs some numbers for the uniforms around):
var pixelated = L.tileLayer.gl({
fragmentShader: fragmentShader,
tileLayers: [hackishTilelayer],
uniforms: {
// The shader will need the zoom level as a uniform...
uPixelatedZoomLevel: hackishTilelayer.options.maxNonPixelatedZoom,
// ...as well as the tile size in pixels.
uTileSize: [hackishTilelayer.getTileSize().x, hackishTilelayer.getTileSize().y]
}
}).addTo(map);
You can see everything working together in this demo.

How to get the edge of a polygon drawn on map within Leaflet

I am working with Leaflet and Leaflet-Draw in Angular to draw some polygons on the Google Map. How can I implement a listener when the user clicks exactly on the edge of the drawn polygons and get the lat and lng of that edge. I know a similar situation can be implemented with Google Map API like the code below, but I can not find any source to help me implement the same thing in Leaflet.
google.maps.event.addListener(polygon, 'click', function (event) { console.log(event.edge) }
Google Map Documentation: https://developers.google.com/maps/documentation/javascript/reference/polygon#PolyMouseEvent
For those who come across this question: I found a solution myself!
I didn't find anything directly from Leaflet draw library that I could use, so I defined the problem for myself as a trigonometry problem and solve it that way.
I defined a function in which on polygon click, it converts the event.latlng and loops over polygon.getLatLngs()[0] taking a pair of A and B points. A is the first coordinates, B is the next and if it reaches to the end of array, B will be the first point. Then using Collinear Function of 3 points with x, y, I checked if the clicked x, y has a same slope as point A and B.(has to be rounded it up), if so, I would save that A and B point pair with their latLng information and further used it in my project.
Although this method works, I would appreciate if anybody would know a better solution or library built-in function that can be used instead. Thanks!
When the user clicks on the polygon you can loop through all corners and check if he clicked in the near of the corner.
poly.on('click', function(e){
var latlng = e.latlng;
var corners = poly.getLatLngs();
if(!L.LineUtil.isFlat(corners)){ //Convert to a flat array
corners = corners[0];
}
//Convert the point to pixels
var point = mymap.latLngToContainerPoint(latlng);
//Loop through each corner
corners.forEach(function(ll){
//Convert the point to pixels
var point1 = mymap.latLngToContainerPoint(ll);
var distance = Math.sqrt(Math.pow(point1.x - point.x, 2) + Math.pow(point.y - point1.y, 2));
//Check if distance between pixels is smaller then 10
if(distance < 10){
console.log('corner clicked');
}
});
});
This is plain JS you have to convert it self to angular.
A alternativ is to place on each corner a DivMarker or a CircleMarker and fire a event if the marker is clicked.
Looks like: https://geoman.io/leaflet-geoman

leaflet editable restrict draw to a specific area

In Leaflet.Editable I want to confine/limit my customers to draw only in a specific area/bounds.
actually im trying to limit them to (90, -90, 180, -180) bounds of map..
maxBounds: [[-90, -180], [90, 180]]
I was not able to find anything anywhere and it seems that i am missing something.
CODEPEN DEMO
please help.
EDIT:
the Y axis is blocking correctly and mouse cannot stretch shape beyond top and bottom.
the problem is in X axis (as seen in pictures)
as for now i solved it with after save check and clear shape if it out of map bounds (BAD USER EXPERIENCE). i need a mouse confinement just like y axis does.
Without knowing your use case (why the whole world map??) Quickest and easiest fix would be to simply set the map's minZoom to something a bit higher, for example, I found that minZoom: 5 was adequate except for cases where the map was both really short and really wide (which is rarely the case in most apps I've seen).
But the real fix involves writing your own custom overrides for dragging markers and shapes.
According to API doc the L.Editable plugin allows you to override a bunch of stuff including the VertexMarker class, via map.editTools.options.vertexMarkerClass.
Fixed codepen: http://codepen.io/anon/pen/GrPpRY?editors=0010
This snippet of code that allows you to constrain the longitude for dragging vertex markers by correcting values under -180 and over 180 is this:
// create custom vertex marker editor
var vertexMarkerClass = L.Editable.VertexMarker.extend({
onDrag: function(e) {
e.vertex = this;
var iconPos = L.DomUtil.getPosition(this._icon),
latlng = this._map.layerPointToLatLng(iconPos);
// fix out of range vertex
if (latlng.lng < -180) {
e.latlng.lng = latlng.lng = -180;
this.setLatLng(latlng);
}
if (latlng.lng > 180) {
e.latlng.lng = latlng.lng = 180;
this.setLatLng(latlng);
}
this.editor.onVertexMarkerDrag(e);
this.latlng.update(latlng);
this._latlng = this.latlng; // Push back to Leaflet our reference.
this.editor.refresh();
if (this.middleMarker) this.middleMarker.updateLatLng();
var next = this.getNext();
if (next && next.middleMarker) next.middleMarker.updateLatLng();
}
});
// attach custom editor
map.editTools.options.vertexMarkerClass = vertexMarkerClass;
I didn't code for dragging the shape as a whole (the rectangle, in this case). While the VertexMarker fix should address all kinds of vertex dragging, you need to override each shape's drag handler to properly constrain the bounds. And if bounds are exceeded, crop the shape appropriately. As was pointed out, Leaflet already does this for latitude, but because Leaflet allows wrapping the map around horizontally you have your essential problem. Using rec.on("drag") to correct the bounds when they cross over your min/max longitude is the only way to address it. It is basically the same solution as I have laid out for the vertexMarkerClass - actual code left as exercise for the diligent reader.

Leaflet latLngToContainerPoint and containerPointToLatLng not reciprocal?

Anybody know why the following is not reciprocal? latLng and new
var point = dispmap.latLngToContainerPoint(latlng);
var newPoint = L.point([point.x, point.y]);
var newLatLng = dispmap.containerPointToLatLng(newPoint);
When I execute this code I send in latlng=(26.75529,-80.93581)
newLatLng, which by inspection of the code above I would expect to reciprocate gives back...
newLatLng = (26.75542,-80.93628)
I'm wanting to array some markers with identical lat-lons around the shared spot on a map, and bumping each by some screen coordinates looks like the best method based on some blog/issue reading I've done.
I'm, "close" to what I want to achieve, but as I try to validate what these leaflet calls are doing for me I hit the fundamental question above.
They can't be ...
Latitude and longitude are float values while x and y are integer values.
This means that there are an (theoretically) infinite number of latlng's and a rather small number of points on your view (width * height).
Furthermore, I'm not sure how you define identical latlng's; the best you can't to is to speak of proximity.
If I read between the lines, identical may mean that the markers overlap. Then the best way is to have a look how Leaflet.MarkerCluster are tackling with the problem.
I was able to achieve my desired result by altering zoom level to avoid pixel-point quantization effects on my translations. The screenshot below illustrates an orange and two green circle markers that represent an identical lat-lon, but I want the green arrayed around the orange in a circular fashion...in this example there are only 2 green.
I perform simple circular array math with an angular step size of PI/4 in this example. The KEY to getting the visual effect correct is the "dispmap.setZoom(dispmap._layersMaxZoom)" call BEFORE I do the math, and then I invoke "dispmap.setZoom(mats.zoom)" after the math, which will give the user the desired zoom level as specified by variable mats.zoom.
var arrayRad=20;
var dtheta=Math.PI/4;
var theta=0;
dispmap.setZoom(dispmap._layersMaxZoom)
L.geoJson(JSON.parse(mats.intendeds), {
pointToLayer: function (feature, latlng) {
var point = dispmap.latLngToContainerPoint(latlng);
dx = arrayRad*Math.cos(theta);
dy = arrayRad*Math.sin(theta);
theta += dtheta;
var newPoint = L.point([point.x + dx, point.y+ dy]);
var newLatLng = dispmap.containerPointToLatLng(newPoint);
return L.circleMarker(newLatLng, intendedDeliveryLocationMarkerOptions);
}, onEachFeature: onEachIntendedLocFeature }).addTo(dispmap);
dispmap.setZoom(mats.zoom);
Sample screen shot at max zoom level: 2 arrayed markers

Leaflet - draw polyline vertices only

The title is quite clear, I'm using Leaflet and I need to show only the vertices of a polyline. For exemple see this image :
Currently I can only have the black line, I'd like only the red squares. Using markers is not an option for performance issue, my lines can be huge (500 000 vertices) and the use of smoothFactor is a need.
Is that possible? If not, does someone knows a plugin that does that, or have a hint on how could I do that by extending the Polyline class?
What you could do here is every time the polyline gets rendered, get the segments of it's SVG path, use those points to add SVG rectangle elements to the polyline's container:
var polyline = L.Polyline([]).addTo(map),
list = polyline._path.pathSegList
// Iterate segments
for (var i = 0; i < list.length; i++) {
// Create SVG rectangle element
rectangle = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
// Set rectangle size attributes
rectangle.setAttributeNS(null, 'height', 4)
rectangle.setAttributeNS(null, 'width', 4)
// Set position attributes, compensate for size
rectangle.setAttributeNS(null, 'x', list[i].x - 2)
rectangle.setAttributeNS(null, 'y', list[i].y - 2)
// Set rectangle color
rectangle.setAttributeNS(null, 'fill', 'red')
// Append rectangle to polyline container
polyline._container.appendChild(rectangle)
}
Seems to work as far as i had time to test it ;) Had to use a timeout though, don't know why, look in to that when i've got more time on my hands.
Example on Plunker: http://embed.plnkr.co/vZI7aC/preview