Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

squareGrid result is only square in the unit of degree #2638

Open
4 tasks
lxydft opened this issue Jun 28, 2024 · 9 comments · Fixed by #2683
Open
4 tasks

squareGrid result is only square in the unit of degree #2638

lxydft opened this issue Jun 28, 2024 · 9 comments · Fixed by #2683

Comments

@lxydft
Copy link

lxydft commented Jun 28, 2024

Please provide the following when reporting an issue:

  • The version of Turf you are using, and any other relevant versions.
  • GeoJSON data as a gist file or geojson.io (filename extension must be .geojson).
  • Snippet of source code or for complex examples use jsfiddle.
  • Verify this issue hasn't already been reported, or resolved in the latest alpha pre-release.
@twelch
Copy link
Collaborator

twelch commented Jun 28, 2024

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
Copy link
Author

lxydft commented Jun 28, 2024

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

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
Copy link
Collaborator

twelch commented Jun 28, 2024

@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) --

return rectangleGrid(bbox, cellSide, cellSide, options);
.

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).

@twelch
Copy link
Collaborator

twelch commented Jun 28, 2024

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

@twelch twelch added the docs label Jun 28, 2024
@lxydft
Copy link
Author

lxydft commented Jun 28, 2024

@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) --

return rectangleGrid(bbox, cellSide, cellSide, options);

.
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
Copy link
Collaborator

twelch commented Jun 28, 2024

@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
Copy link
Collaborator

twelch commented Jun 28, 2024

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
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
Copy link
Member

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.

@twelch twelch reopened this Aug 11, 2024
@twelch twelch changed the title turf V7.0.0 squareGrid result is not square,It can be judged solely by the naked eye. squareGrid result is only square in the unit of degree Aug 11, 2024
@twelch
Copy link
Collaborator

twelch commented Aug 11, 2024

Keeping this issue open for visibility. The current behavior of rectangle-grid and square-grid is now at least a little better documented (visible once the 7.1.0 API docs are published).

But the current behavior of these functions is not optimal. A code workaround can be found above to get the behavior in 6.4.0 and prior, where the distance between grid cell sides will match the length and unit passed.

Discussion has at least started about next steps, feedback welcome there - #2640

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants