Creating a circle with Leaflet (using a positive radius) - javascript

Making a circle in Leaflet seems pretty straight forward according to the documentation, you just supply coordinates, a radius and optionally an options object:
L.circle( <LatLng> latlng, <Number> radius, <Path options> options? )
I tried doing this on a map I was working on but only managed to produce a dot instead of a circle, no matter how large of a radius I specified. I let this drive me nuts for a while and then stepped away from the code to work on other projects. I recently came back to it and on a whim I tried using a negative value for the radius:
"use strict";
var theMap = L.map("map", {
minZoom: 0,
maxZoom: 7,
crs: L.CRS.Simple
}).setView([0, 0], 0),
eventsLayer = new L.LayerGroup(),
unproject = function (coord) {
return theMap.unproject(coord, theMap.getMaxZoom());
},
coord = unproject([12057.79, 13661.21]),
southWest = unproject([0, 32768]),
northEast = unproject([32768, 0]),
radius = -8653.18, // Why isn't it working with a positive radius?!?!?
circ = L.circle(coord, radius, {color: '#f00', fillColor: '#00f'});
theMap.setMaxBounds(new L.LatLngBounds(southWest, northEast));
L.tileLayer("https://tiles.guildwars2.com/1/1/{z}/{x}/{y}.jpg", {
minZoom: 2,
maxZoom: 7,
continuousWorld: true
}).addTo(theMap);
eventsLayer.addTo(theMap);
theMap.setView(coord, 7, {animate: true});
circ.addTo(eventsLayer);
For some reason it creates a circle with the negative radius.
This seems like a bug since the documentation uses a positive value for radii. Is there something I did wrong in my code that is causing this buggy behavior, or is it possibly a bug in Leaflet?

After coming across this answer I think it is a bug in Leaflet. I downloaded the current version of the dev master and added into the fiddle.
radius = 1,
The new version does accept positive values and makes a circle. It is however buggy in a different way. 0.7 specifies that the radius to a circle should be set in meters. In the current dev version even a value of 1 covers hundreds of meters.

Related

OpenLayers draw LineString with real width in meters

I tried to draw a polyLine (road lane) with width in meters using the latest stable OpenLayers library, but it didn't have the correct real width (checked on top of Google Maps layer).
This is the code snippet that works for me:
polyLineFeature.setStyle((feature, resolution) => {
const lineString = feature.getGeometry();
const lineStyles = [
new Style({
stroke: new Stroke({
width:
laneWidth /
resolution /
getPointResolution("EPSG:3857", 1, lineString.getCoordinateAt(0.5)),
lineCap: "square",
}),
}),
];
return lineStyles;
});
I use the default projection everywhere (web mercator).
But I don't get the reason why do I have to divide the real width in meters by the current resolution AND another resolution that is relevant within the actual area.
Is this explained anywhere?
WebMercator 3857 is a bad projection with respect to distances. It introduces a huge distortion as you move away from the equator (that's why Greenland looks so big on the map while in reality it is a fairly small piece of land).
By dividing the width by getPointResolution, you are effectively correcting this distortion.
If you were using another projection that preserve distances, such as UTM, you wouldn't have to do this division (or, if you did, the value of getPointResolution would be 1)

Leaflet: misalignment of tiles and negative y coordinates

I am trying to generate a custom non-geographical map with Leaflet using my own tiles.
For the moment I created 10x10 dummy tiles of size 256x256px each with a random solid color using imagemagick.
They are placed in public/map-tiles/1/map_x_y.png where x takes values from 0 to 9 (resp. y).
Since this is a non-geographical map, I paid attention to change crs to L.CRS.Simple (see http://leafletjs.com/examples/crs-simple/crs-simple.html):
var map = L.map('map', {
minZoom: 1,
maxZoom: 1,
center: [0, 0],
crs: L.CRS.Simple
}).setView([0, 0], 1);
L.tileLayer('/map-tiles/{z}/map_{x}_{y}.png', {
bounds: [[0, 0], [2560, 2560]],
tms: true
}).addTo(map);
However this produces tiles slightly shifted and thus misaligned:
Also, tiles with negative y coordinates are fetched, which results in 404d requests, as seen in the console.
What can be the cause of this behavior?
EDIT 1: as IvanSanchez pointed out, the misalignment was caused by the missing leaflet.css stylesheet.
I still have the problem with negative coordinates. As suggested, I added bounds (see updated code above). Observations:
with bounds [[0, 0], [2560, 2560]]: no tiles displayed altogether, blank screen.
with bounds [[-1280, -1280], [1280, 1280]]: all tiles displayed, but negative tiles fetched (eg (5,-1)) resulting in 404s.
EDIT 2: after several tests it looks like the negative tiles are indeed the product of the y-axis handling (http://leafletjs.com/examples/wms/wms.html). The origin is top left, y going downward. I expected the tiles below the origin to be fetched, not above.
What I tried in order to keep my convention with x and y both increasing (that is x increases to the right, y increases downward, tiles with positive coordinate components are fetched from 0 to 9):
setting tms: true for tileLayer. No success, tiles with negative y are still fetched .
changing {y} to {-y} in the tileLayer source path. Results in Error: No value provided for variable {-y}. My script is using Leaflet 1.3.1.
In a map with L.CRS.Simple, all TileLayers have infinite bounds by default.
If you want a TileLayer to request tiles only in a given area, read the Leaflet API documentation, specifically a TileLayer option named bounds (inherited from GridLayer options). Let me quote:
bounds
type LatLngBounds
default undefined
If set, tiles will only be loaded inside the set LatLngBounds.
You also mention:
Weirdly enough, it tries to fetch tiles with negative coordinates [...]
That's not weird, it's behaviour as designed. There is nothing inherently wrong (nor weird) with negative coordinates, and negative tile coordinates are valid and documented in some tile standards
So if you have 10x10 tiles of 256px in size ranging from [0, 0] to [10, 10], you might want something like
L.tileLayer('/map-tiles/map_{x}_{y}.png', {
bounds: [[0, 0], [2560, 2560]]
}).addTo(map);
If the center of your data is the [0, 0] point and your tiles span from [-5, -5] to [5, 5] you might instead want something like
L.tileLayer('/map-tiles/map_{x}_{y}.png', {
bounds: [[-1280, -1280], [1280, 1280]]
}).addTo(map);
The problem boils down to two aspects:
misalignment: the leaflet.css stylesheet was missing and simply needed to be linked to in the HTML of the page.
negative tiles fetched: for Leaflet the y-axis goes downward. I expected tiles to be fetched from left to right, top to bottom with an origin set to the top left corner. Instead, negative y's were fetched. Since my tiles' names are map_x_y.png where x and y take values in {0:9}, this resulted in 404d requests. Setting negative bounds fixed the issue with bounds: [[0,0],[-1230,1230]] (notice the minus sign). 1230 corresponds to the number of tiles at zoom 0 times the size in pixel of one tile.

Global map tiles disappear past zoom level 19

I use OpenStreetMap with Leaflet.js.
I have a map with an indoor picture on it. The problem is when I zoom in, streets disapears. Do you know anything that can solve this plz? Tricks or tips!
EDIT:
// Load the Map
this.map_ = L.map($(selector)[0], {
center: [
48.8459382,
2.2863024,
],
maxZoom: 24,
zoom: 20,
});
I guess you have used map.options.maxZoom at a high number to let the user zoom to see your indoor image details.
However, OSM tiles are not available past zoom level 19, so the server returns 404 errors and your tiles are replaced by the Error Tile (or just a grey tile if not specified).
In that case, you would simply need to use these 2 options (together) on Tile Layer to tell Leaflet to re-use tiles from a lower zoom and to expand them:
maxNativeZoom set at 19.
maxZoom set at whatever you need, and equal to map.options.maxZoom if specified.
L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', {
maxNativeZoom: 19, // OSM max available zoom is at 19.
maxZoom: 22 // Match the map maxZoom, or leave map.options.maxZoom undefined.
}).addTo(map);
Demo: http://jsfiddle.net/ve2huzxw/68/

Leaflet: Are custom zoom levels possible?

Is it possible to have intermediate (2.5, 3.5, 4.5, etc.) zoom levels on a Leaflet map that is using Stamen Toner-lite tiles? This is the code I have so far that calculates the zoom level:
leafletmap.on('zoomstart', function (d){
targetZoom = leafletmap.getZoom(); //Grabs whatever current zoom level is
targetZoom = targetZoom +.5; //Adds .5
leafletmap.setZoom(targetZoom); //Sets new value to zoom level
console.log(targetZoom); //Consoles out new value
});
I tried just adding .5 to the code, but I get a too much recursion error, so I'm guessing it's not that simple. Any help or direction is greatly appreciated!
In version 1.0.0, Leaflet introduced fractional zooming:
https://leafletjs.com/examples/zoom-levels/#fractional-zoom
Before this, the zoom level of the map could be only an integer number
(0, 1, 2, and so on); but now you can use fractional numbers like 1.5
or 1.25.
...
If you set the value of zoomSnap to 0.5, the valid zoom levels of the
map will be 0, 0.5, 1, 1.5, 2, and so on.
If you set a value of 0.1, the valid zoom levels of the map will be 0,
0.1, 0.2, 0.3, 0.4, and so on.
The following example uses a zoomSnap value of 0.25:
var map = L.map('map', {
zoomSnap: 0.25
});
As you can see, Leaflet will only load the tiles for zoom levels 0 or
1, and will scale them as needed.
Leaflet will snap the zoom level to the closest valid one. For
example, if you have zoomSnap: 0.25 and you try to do
map.setZoom(0.8), the zoom will snap back to 0.75. The same happens
with map.fitBounds(bounds), or when ending a pinch-zoom gesture on a
touchscreen.
To be straight to the point: This is not possible. You would need to render your own tile-images, run them of your own server and create your own coordinate reference system (CRS) extension for Leaflet. If you look at how regular tilesets are made you'll understand why.
The URL for requesting tiles for stamen:
http://{s}.tile.stamen.com/toner/{z}/{x}/{y}.png
When requesting tiles, the {z} will be replaced with the map's current zoomlevel. The {x} and {y} are the coordinates for the tile. The {s} will be replaced with a subdomain. So if your at zoomlevel 6 at coordinate 1,1 it would try to load:
http://a.tile.stamen.com/toner/6/1/1.png
Now if you could (but you can't) zoom to level 6.5 it would try to load:
http://a.tile.stamen.com/toner/6.5/1/1.png
Those tiles simple don't exists on the stamen server and thus return a 404 for file not found. You can try for yourself just use these links:
http://a.tile.stamen.com/toner/6/1/1.png
http://a.tile.stamen.com/toner/6.5/1/1.png
http://a.tile.stamen.com/toner/7/1/1.png
So that will never work. You could, as said, run your own tile server, render your own tile images and setup your own L.CRS. You might want to take a look at this question too: Adding an extra zoom levels in Leaflet Maps

Leaflet: Circle behaving different from CircleMarker

In the documentation for Leaflet here: http://leafletjs.com/reference-1.2.0.html#circlemarker it says that CircleMaker extends Circle, and that it is the same thing, except the radius is specified in pixels rather than in meters, so that the circles stay constant size even if you zoom the map.
I do however need Circles, because I am trying to draw 100m radius circles on a map. To do this, I use the following code:
var geojsonLayer = new L.GeoJSON(null,{
pointToLayer: function (latlng){
return new L.CircleMarker(latlng, {
radius: 5,
fillColor: "#ff7800",
color: "#000",
weight: 1,
opacity: 1,
fillOpacity: 0.8,
});
}});
map.addLayer(osm);
map.addLayer(geojsonLayer);
geojsonLayer.addGeoJSON(jsonExample);
This works perfectly, however, if I change the code to use "Circle" instead of CircleMaker the entire map fails to load, and I get a javascript error:
Error: Error: Invalid LatLng object: (56.229917, NaN)
I can fix this by pre-filtering the geojson to remove those points which lack both latitude and longitude, but I'm confused: Circle and CircleMaker both specify that they take a LatLng-object as the specification of the center-point, I don't get how a certain LatLng object can be valid as centre-point for a CircleMarker, but invalid if used as the centerpoint for a Circle.
Am I overlooking something obvious, or is this just a weakness and/or bug in Leaflet that I'll just have to work around ?
There is no problem with the code anymore. Though this same issue can arise easily because of the different constructors:
L.CircleMarker( <LatLng> latlng, <Path options> options? )
and
L.Circle( <LatLng> latlng, <Number> radius, <Path options> options? )
Be sure to pass the radius to your new circles.
I fixed this by changing the _getLngRadius() method of leaflet.js in L.circle. In leaflet version 0.4.4, it's around line 4913.
This method differs from the one in circleMarker, as it computes the radius of the circle dynamically. If you change the line that has
this._mRadius/hLength
to
this._mRadius.radius/hLength
it should be ok.
You can add if(latlng && latlng.lat && latlng.lng) at the begining of your function (latlng)
that will ignore the faulty datas.

Categories

Resources