I had been researching this issue for quite a while as I have developed an application that allows the user to generate an Area of Interest rectangle either via a DragBox action or plotting User entered extent points. When I started this adventure I was completely new to OpenLayers. The problem with the manually entered extent points was that if AOI covered the International Dateline the rectangle drawn would be drawn the wrong way around the world. Numerous StackExchange users have asked about this problem only to be told by an OpenLayers responder that (and I'm paraphrasing here) "OpenLayers has no way of knowing the directional intent of the points to be drawn so it defaults...". Uh, I have to raise the BS flag on that response as I have now learned enough about OpenLayers to be dangerous and this issue has been happening to me. The problem I have with their response is that I load the coordinates for an extent which, by definition, specifies the Upper Right Longitude and Latitude as well as the Lower left Longitude and Latitude. If the Upper Right Longitude lies on the Western side of the IDL and the Lower Left Longitude lies on the Eastern side of the IDL it is pretty obvious which way the user wants to plot the polygon and yet OpenLayers insists on swapping the Longitudinal values and drawing the polygon the wrong way around the world. A sample of the extent declaration and problematic OpenLayers method call is shown below.
// I would start out with the following entered values as an example
lonLL = 175.781; // minX
latLL = 13.992; // minY
lonUR = -165.937;// maxX
latUR = 25.945; // maxY
// I would then make the following call
var manCoordEntryExtent = ol.extent.boundingExtent([[lonLL,latLL], [lonUR, latUR]]);
// Looking at the resulting structure in the debugger I get:
0: -165.937 // minX
1: 13.992 // minY
2: 175.781 // maxX
3: 25.945 // maxY
length: 4
__proto__: []
As you can see the Longitudinal coordinates get reversed and so after you then create the full coordinate structure, a polygon. a polygonFeature and then apply that feature to a vector and finally plot it only to find that the polygon goes the wrong way around the world.
I needed to figure out why this was happening so I dug into this ol.extent.boundingExtent method in the OpenLayers 4 library.
/**
* Build an extent that includes all given coordinates.
*
* @param {Array.<ol.Coordinate>} coordinates Coordinates.
* @return {ol.Extent} Bounding extent.
* @api
*/
ol.extent.boundingExtent = function(coordinates) {
var extent = ol.extent.createEmpty();
for (var i = 0, ii = coordinates.length; i < ii; ++i) {
ol.extent.extendCoordinate(extent, coordinates[i]);
}
return extent;
};
It first calls ol.extent.createEmpty to initially create an extent structure
/**
* Create an empty extent.
* @return {ol.Extent} Empty extent.
* @api
*/
ol.extent.createEmpty = function() {
return [Infinity, Infinity, -Infinity, -Infinity];
};
// It then iterates thru the number of coordinates and fills in the extent structure values, however...
// Here is where the problem is. Notice the complete lack of any explanation as to what the hell this
// method is doing. Why is it doing what it does? All I know is that it cannot handle plots across
// the IDL and it corrupts your extent structure if you try.
/**
* @param {ol.Extent} extent Extent.
* @param {ol.Coordinate} coordinate Coordinate.
*/
ol.extent.extendCoordinate = function(extent, coordinate) {
if (coordinate[0] < extent[0]) {
extent[0] = coordinate[0];
}
if (coordinate[0] > extent[2]) {
extent[2] = coordinate[0];
}
if (coordinate[1] < extent[1]) {
extent[1] = coordinate[1];
}
if (coordinate[1] > extent[3]) {
extent[3] = coordinate[1];
}
};
// The solution was for me to test for IDL myself and if found then create an empty extent and populate it myself manually.
// Using the same extent coordinates as before
lonLL = 175.781; // minX
latLL = 13.992; // minY
lonUR = -165.937;// maxX
latUR = 25.945; // maxY
// I test for Dateline instance (Dont have to worry about the potential of there being a polygon covering both Meridian
// and Anti-meridian as a valid polygon is limited to a maximum size of just over 12 million square kilometers.)
if ((lonLL > 0.0) && (lonUR < 0.0)) {
// Manually build the coordinates for the Area calculation as the boundingExtent
// codepath corrupts an extent to be plotted across the Dateline
var manCoordEntryExtent = ol.extent.createEmpty();
manCoordEntryExtent[0] = lonLL;
manCoordEntryExtent[1] = latLL;
manCoordEntryExtent[2] = lonUR + 360.0;
manCoordEntryExtent[3] = latUR;
} else {
var manCoordEntryExtent = ol.extent.boundingExtent([[lonLL,latLL], [lonUR, latUR]]);
}
// Looking at the resulting structure in the debugger I get:
0: 175.781 // minX
1: 13.992 // minY
2: 194.063 // maxX
3: 25.945 // maxY
length: 4
__proto__: []
My code calculates the area dynamically so that I can determine if the User has created a valid sized AOI polygon. When I am processing a DragBox generated selection I am requesting the coordinates from the resulting geometry structure and for an EPSG:4326 projection when it returns coordinates from a wrapped world the coordinates past the first 180.0 degrees continue incrementing thus the reason for the lonUR calculation of 360.0 - 165.937 = 194.063. My area calculation codepath uses the following IDL test and in order to use the same codepath for the manually entered coordinates I needed to simulate the coordinate value as if it had been returned from the DragBox getGeometry call. I'm actually testing a GEOJSON polygon structure which is a 3 dimensional array with the 1st dimension being the Ring number, the 2nd the X index and 3rd the Y index.
function getArea(coords, extent) {
// Test for Western side of Dateline instance
if (((coords[0][0][0] <= -180.0) && (coords[0][2][0] > -180.0)) ||
// Test for Eastern side of Dateline instance
((coords[0][0][0] < 180.0) && (coords[0][2][0] >= 180.0))) {
.
.
.
If these tests pass at this point the code uses the algorithm I developed to calculate the area over the IDL otherwise it just calculates it as normal everywhere else.
I then use this extent to create a polygon, then a polygonFeature, then apply that feature to a vector and finally plot it and this time it plotted correctly. So the fix I came up with to help solve the area calculation problem I was having also fixed the plotting problem.
Maybe this solution will help someone else or get them thinking in a different direction. The solution came to me when I was finally able to break the problem of the IDL into two issues. The actual area calculation was one issue with the other being the plotting of the polygon over the IDL.