Turfjs / turf

A modular geospatial engine written in JavaScript and TypeScript
https://turfjs.org/
MIT License
9k stars 925 forks source link

turf V7.0.0 squareGrid result is not square,It can be judged solely by the naked eye. #2638

Open lxydft opened 1 week ago

lxydft commented 1 week ago

Please provide the following when reporting an issue:

twelch commented 1 week ago

Example please. What are the coordinates of your square grid? Are you sure this is a turf issue and not the behavior of your map client?

lxydft commented 1 week ago

请举例说明。你的方形网格的坐标是什么?您确定这是地盘问题而不是地图客户端的行为吗?

Firstly, thank you for your reply. As shown in the code below, distance and distance1 are not equal. Is there a problem with my understanding?The current calculation should not be related to the map engine, I am using Cesium. Looking forward to your criticism and correction.

let squareGrid = turf.squareGrid([-95, 30 ,-85, 40], 50, {
  units:'miles'
});

let arr = squareGrid.features[0].geometry.coordinates[0]

let from = turf.point(arr[0]);
let to = turf.point(arr[1]);
let options = {units: 'miles'};
let distance = turf.distance(from, to, options);

let from1 = turf.point(arr[1]);
let to1 = turf.point(arr[2]);
let distance1 = turf.distance(from1, to1, options);
console.log(distance,distance1);  // 50.00000000000005 42.84935098373633
twelch commented 1 week ago

@lxydft you are right, the distance in miles between your example points, in the X and Y direction, are not the same. But I do believe they are the same distance apart in degrees, which is the measure of square produced by this function.

Here is some additional log statements for your code to demonstrate:

console.log(JSON.stringify(arr[0]))
console.log(JSON.stringify(arr[1]))
console.log(JSON.stringify(arr[2]))
console.log("distanceDeg1", arr[0][1] - arr[1][1])
console.log("distanceDeg2", arr[1][0] - arr[2][0])

Output:

[-94.70377645217319,30.296223547826816]
Console.js:82 [-94.70377645217319,31.01988146354577]
Console.js:82 [-93.98011853645424,31.01988146354577]
Console.js:82 distanceDeg1 -0.7236579157189524
Console.js:82 distanceDeg2 -0.7236579157189453

The distance in degrees between your two example points in degrees is approximately the same, to within 13 decimal points which is very small.

But calling turf.distance on those points produces a variable distance in miles. I believe this has to do with the haversine formula used by turf.distance and the nature of how a distance of 0.72 degrees is different in miles for longitude and latitude depending on where the points are on the Earth.

I can leave this ticket open for documentation to be improved.

I don't have a good answer on how to produce what you want. perhaps you can study how turfSquare works (it actually calls turfRectangle) -- https://github.com/Turfjs/turf/blob/a8038d0be211c9006cc6883087104ba230857b5a/packages/turf-square-grid/index.ts#L45.

See https://github.com/Turfjs/turf/blob/master/packages/turf-rectangle-grid/index.ts

There are some past issues where people have found this function to not behave as expected: https://github.com/Turfjs/turf/issues/1214#issuecomment-355941084.

twelch commented 1 week ago

Here's a link that explains a little more about the variation in distance between longitude and latitude depending on where you are on earth -- https://www.usgs.gov/faqs/how-much-distance-does-a-degree-minute-and-second-cover-your-maps

lxydft commented 1 week ago

@lxydft you are right, the distance in miles between your example points, in the X and Y direction, are not the same. But I do believe they are the same distance apart in degrees, which is the measure of square produced by this function.

Here is some additional log statements for your code to demonstrate:

console.log(JSON.stringify(arr[0]))
console.log(JSON.stringify(arr[1]))
console.log(JSON.stringify(arr[2]))
console.log("distanceDeg1", arr[0][1] - arr[1][1])
console.log("distanceDeg2", arr[1][0] - arr[2][0])

Output:

[-94.70377645217319,30.296223547826816]
Console.js:82 [-94.70377645217319,31.01988146354577]
Console.js:82 [-93.98011853645424,31.01988146354577]
Console.js:82 distanceDeg1 -0.7236579157189524
Console.js:82 distanceDeg2 -0.7236579157189453

The distance in degrees between your two example points in degrees is approximately the same, to within 13 decimal points which is very small.

But calling turf.distance on those points produces a variable distance in miles. I believe this has to do with the haversine formula used by turf.distance and the nature of how a distance of 0.72 degrees is different in miles for longitude and latitude depending on where the points are on the Earth.

I can leave this ticket open for documentation to be improved.

I don't have a good answer on how to produce what you want. perhaps you can study how turfSquare works (it actually calls turfRectangle) --

https://github.com/Turfjs/turf/blob/a8038d0be211c9006cc6883087104ba230857b5a/packages/turf-square-grid/index.ts#L45

. See https://github.com/Turfjs/turf/blob/master/packages/turf-rectangle-grid/index.ts

There are some past issues where people have found this function to not behave as expected: #1214 (comment).

Thank you very much for your reply. I used a square grid mainly for the convenience of obtaining squares for some calculations. I remember that in previous versions, rectangular grids could be used to obtain squares. I couldn't find the code and could only find some screenshots. I thought I deleted this method in the 7.0 version of the document, but I just tested it and it still works. However, the current results cannot obtain a square in either miles or kilometers. So I think this method may cause a lot of trouble for users if it is only a square in the dimension of degree. I will continue to monitor it. 联想截图_20240628154424

twelch commented 1 week ago

@lxydft I wonder if [rhumbDestination](https://turfjs.org/docs/api/rhumbDestination might be able to be used to create what I think you want, equidistance measured in a fixed length unit.

twelch commented 1 week ago

Okay, I think the behavior you are looking for @lxydft was actually lost in this PR, back in 2021 -- https://github.com/Turfjs/turf/pull/2106/files#, where for reasons explained better there, Turf switched from calculating cellSide using turf.distance, to using turf.convertLength. This may have been done to optimize for certain use cases, while not realizing the impact to others.

Pre-2021 Here's the pre-2021 version of rectangleGrid code below that produces results that are more square, according to the units you pass. Can you give this a try?

Code ```javascript const rectangleGrid = ( bbox, cellWidth, cellHeight, options ) => { // Containers const results = []; const west = bbox[0]; const south = bbox[1]; const east = bbox[2]; const north = bbox[3]; const xFraction = cellWidth / turf.distance([west, south], [east, south], options); const cellWidthDeg = xFraction * (east - west); const yFraction = cellHeight / turf.distance([west, south], [west, north], options); const cellHeightDeg = yFraction * (north - south); // rows & columns const bboxWidth = east - west; const bboxHeight = north - south; const columns = Math.floor(bboxWidth / cellWidthDeg); const rows = Math.floor(bboxHeight / cellHeightDeg); // if the grid does not fill the bbox perfectly, center it. const deltaX = (bboxWidth - columns * cellWidthDeg) / 2; const deltaY = (bboxHeight - rows * cellHeightDeg) / 2; // iterate over columns & rows let currentX = west + deltaX; for (let column = 0; column < columns; column++) { let currentY = south + deltaY; for (let row = 0; row < rows; row++) { const cellPoly = turf.polygon( [ [ [currentX, currentY], [currentX, currentY + cellHeightDeg], [currentX + cellWidthDeg, currentY + cellHeightDeg], [currentX + cellWidthDeg, currentY], [currentX, currentY], ], ], options.properties ); if (options.mask) { if (turf.intersect(options.mask, cellPoly)) { results.push(cellPoly); } } else { results.push(cellPoly); } currentY += cellHeightDeg; } currentX += cellWidthDeg; } return turf.featureCollection(results); } //// EXAMPLE CODE //// let squareGrid = rectangleGrid([-95, 30 ,-85, 40], 50, 50, { units:'miles' }); let arr = squareGrid.features[0].geometry.coordinates[0] let from = turf.point(arr[0]); let to = turf.point(arr[1]); let options = {units: 'miles'}; let pointDistance = turf.distance(from, to, options); console.log(JSON.stringify(arr[0])) console.log(JSON.stringify(arr[1])) console.log(JSON.stringify(arr[2])) console.log("distanceDeg1", arr[0][1] - arr[1][1]) console.log("distanceDeg2", arr[1][0] - arr[2][0]) let from1 = turf.point(arr[1]); let to1 = turf.point(arr[2]); let distance1 = turf.distance(from1, to1, options); console.log(pointDistance,distance1); return turf.featureCollection([squareGrid, from, to, from1, to1]); ```

Produces nearly equal cellSides in miles

let squareGrid = rectangleGrid([-95, 30 ,-85, 40], 50, 50, {
  units:'miles'
});

-0.7236579157189524 -0.8358739977126532 (distance between sides in degrees)
50.00000000000005 49.49387880600235 (distance between sides in miles)

image

Turf 7.0

Almost exactly equal cellSides by degrees only, regardless of units passed. More accurate but optimizes for degrees.

let squareGrid = rectangleGrid([-95, 30 ,-85, 40], 50, 50, {
  units:'miles'
});

-0.7236579157189524 -0.7236579157189453 (distance between sides in degrees)
50.00000000000005 42.84935098373633 (distance between sides in miles)

image

The best way forward might be to acknowledge if necessary, that there is no one size fits all, and have it use different algorithms under the hood based on the units passed.

smallsaucepan commented 1 week ago

The best way forward might be to acknowledge if necessary, that there is no one size fits all, and have it use different algorithms under the hood based on the units passed.

That doesn't sound ideal. It wouldn't be great if the results change drastically depending on the units used.

Looking at #2106 I get the feeling rectangle-grid was working ok, except for when people tried to do it over massive areas. Perhaps we look at reverting the calculation method and direct some effort toward making that work in a wider variety of circumstances.