Is there a way to get a shape to wrap from one edge across the dateline meridian (180° longitude) to appear on the other side of the map in Leaflet.js?
I've looked at:
http://leafletjs.com/reference.html#latlng-wrap
https://github.com/Leaflet/Leaflet/issues/82
But I'm unsure on what I could do to get it to reliably draw across the dateline...
Thank you in advance!
Oh, you're hitting antimeridian artifacts. You're not the first one, and will not be the last one.
In Leaflet, there are basically two approaches for this problem:
1a: Cut the polygon beforehand
If you know your GIS tools, preprocess your polygon, so you end up with two (or possibly more) polygons. See «How can I make a polyline wrap around the world?».
Once you have a file with several polygons which don't cross the antimeridian, they should render fine. You will hit artifacts (namely, a vertical polygon border at the antimeridian, spanning the inside ofthe polygon) if you apply a border to the polygons, so you might want to cut a polygon and a polyline with the polygon's edge if you want to render both nicely.
1b: Cut the polygon on the browser
If you don't want to cut the polygon beforehand, you can let the web browser do it on the fly.
There are some utilities that can help here, but I'm going to point to Leaflet.VectorGrid in particular. By leveraging geojson-vt, it can cut polygons and their edges into tile-sized polygons and polygon edges. It can handle geometries crossing the antimeridian quite well.
You might want to look into geojson-vt directly, or maybe turf.js to do some on-the-fly geoprocessing.
2: Think outside the [-180..180] range
Leaflet can handle longitudes outside the [-180..180] range. In Leaflet, longitudes wrap only the TileLayer's tiles and not markers or polylines.
In other words: a marker at [0, -179] is shown at a different place than [0, 181]. See this answer for an example.
In other words: a line from [0, 179] to [0, -179] is 358 degrees long, but a line from [0, 179] to [0, 181] is two degrees long.
In other words: you can have linestrings or polygons with coordinates with longitudes outside the [-180..180] range, and that's fine for Leaflet. It's not fine for a lot of GIS software (in fact, I think that the new GeoJSON spec prohibits it). But it will make Leaflet happy.
When you are working with a cylindrical projection, as Leaflet does, it can be solved relatively easily with trigonometry. My solution is based on the first approach of Ivan's answer above, which is cutting the line in two parts at the 180th meridian. My solution is not perfect, as I will show below, but it is a good start.
Here is the code:
function addLineToMap(start, end) {
if (Math.abs(start[1] - end[1]) > 180.0) {
const start_dist_to_antimeridian = start[1] > 0 ? 180 - start[1] : 180 + start[1];
const end_dist_to_antimeridian = end[1] > 0 ? 180 - end[1] : 180 + end[1];
const lat_difference = Math.abs(start[0] - end[0]);
const alpha_angle = Math.atan(lat_difference / (start_dist_to_antimeridian + end_dist_to_antimeridian)) * (180 / Math.PI) * (start[1] > 0 ? 1 : -1);
const lat_diff_at_antimeridian = Math.tan(alpha_angle * Math.PI / 180) * start_dist_to_antimeridian;
const intersection_lat = start[0] + lat_diff_at_antimeridian;
const first_line_end = [intersection_lat, start[1] > 0 ? 180 : -180];
const second_line_start = [intersection_lat, end[1] > 0 ? 180 : -180];
L.polyline([start, first_line_end]).addTo(map);
L.polyline([second_line_start, end]).addTo(map);
} else {
L.polyline([start, end]).addTo(map);
}
}
This will calculate the latitude where the line crosses the 180th meridian, and draw the first line from the starting point to this latitude on the 180th meridian, and then a second one from this point to the end.
The picture below shows an example of the result.
Even though I'm fairly certain the math checks out on my calculations, there is a small kink where the two lines are separated. I'm not sure whether this is due to the rendering of the Leaflet map, or an actual error in my calculations.
The starting point is [35.552299, 139.779999] and the end point is [64.81510162, -147.8560028].
The total longitudinal difference between the points is 72.364, and latitudinal difference is 29.263. Using the code below or an online calculator, the angle α is 22.018. Taking only the distance from the starting point to the 180th meridian, and the angle α, the latitudinal difference between starting point and intersection is 16.264. Adding the latitude of the starting point and this value, we get a latitude of 51.8166 at the 180th meridian. Drawing a straight line on a map tells me that this value should be slightly higher up, but I can't figure out why or how that is calculated.
If you want a curved line that accurately shows the curvate of the earth, I would highly recommend using Leaflet.Geodisic. It is easy to use and has a solution to the antimeridian problem built-in so you don't have to worry about it.
If you're using react-leaflet, the easiest way is to use it together with leaflet.geodesic and set the lines with leaflet.geodesic.
Import the required libraries
import { GeodesicLine } from "leaflet.geodesic"
import * as L from "leaflet"
import {
MapContainer,
} from "react-leaflet"
Some points to join up across the meridian
const East = new L.LatLng(-41.75412, 175.70595)
const FurthurEast = new L.LatLng(-42.43624, -178.65339)
const Chile = new L.LatLng(-26.74165, -71.41818)
In the return of your component:
<MapContainer
center={centerMarker as [number, number]}
zoom={5}
whenCreated={(mapInstance) => {
new GeodesicLine([East, FurthurEast, Chile], {
weight: 10,
color: "red",
}).addTo(mapInstance)
}}
>
{...children}
</MapContainer>
I have set of lat/long which are stored in database and I have to find all the lat/long from the database which are lies between the given lat/long(e.glatitude = 37.4224764,and longitude=-122.0842499) and given radius(say 5miles).For this I need to do some addition and subtraction like:
37.4224764 + 5mile
37.4224764 - 5mile
and
-122.0842499+5mile
-122.0842499-5mile
But I don't know how the conversion works here.
You're a little vague in your requirements, do you just want to find out the coordinates of a point a certain distance from your current location in a given direction? Or get a circle of a radius of a particular distance around your coordinates? Or draw a line of a certain length from your coordinates to another point a particular distance away?
I assume you'd probabaly want to use the Geometry spherical library, e.g.
var latLng = new google.maps.LatLng(geometry.location.lat, geometry.location.lng);
var newLatLng = google.maps.geometry.spherical.computeOffset(
latLng,
1000,
0
);
see also https://developers.google.com/maps/documentation/javascript/geometry#Distance
PS: you need to add the geometry library parameter when loading the map:
<script src="https://maps.googleapis.com/maps/api/js?v=3&libraries=geometry"></script>
If I have line like this (a xy plot in the future)
`Canvas {
id:canvas
onPaint:{
var ctx = canvas.getContext('2d');
ctx.moveTo(0,0);
ctx.lineTo(50,20);
ctx.lineTo(50,70);
// etc...
}
}`
so, is there a good way to check mouse press event on this line? i.e. If I have a plot and want to show context menu on right click on graph line
Unfortunately, AFAIK, you should keep track of what you draw. Canvas doesn't store drawn stuff as vector graphics(again, AFAIK), but on a raster, so there's no way to get it to tell you where the lines/points are.
You could push each point you pass to ctx.moveTo and ctx.lineTo to a list or other structure. Then, on click, iterate over all line segments (designated by pairs of points you stored) and check if distance of the clicked point to the line segment is within some selection tolerance distance you want.
To check the distance, you can use this:
Shortest distance between a point and a line segment
I don't know if this is the simplest way, but it works.
I have a map that already contains markers. I will like to be able to specific a circle radius around a specified location and select all markers that resides within.
The code to added the Circle is done. I need to know how to get the markers.
Can anyone help...
Assuming you have the markers in an array then you can calculate the distance from each of marker to the center of your circle using the computeDistanceBetween function, and add to a second array only those whose distance <= radius.
Flex 3, ActionScript 3, Flash player 9.
I have a picture in a BitmapData object. And an array of points. I nead to erase the part of the picture inside a polygon specified by the points. In other words, draw a polygon specified by the points and fill it with transparency.
Any ideas on how it can be done?
Got it working with the following code:
var shape:Shape = new Shape();
shape.graphics.beginFill(0x000000, 1); // solid black
shape.graphics.moveTo(points[0].x, points[0].y);
points.forEach(function (p:Point, i:int, a:Array):void {
shape.graphics.lineTo(p.x, p.y);
});
shape.graphics.endFill();
data.draw(shape, null, null, "erase");
For a rectangle, you can use fillRect. For a polygon you are gonna have to draw the polygon in a totally different color (than other colors in the bitmap) and use floodFill - but I don't know how to draw a polygon. There is no method in bitmap data class to draw lines. Another option would be to write your own logic to find pixels inside the polygon and use setPixel32 method to set their alphas to zero.
This wikipedia page describes algorithms to find if a point is inside a given polygon. You might find it useful.