d3 / d3-geo-polygon

Clipping and geometric operations for spherical polygons.
https://observablehq.com/collection/@d3/d3-geo-polygon
Other
107 stars 22 forks source link

geoDesic #72

Open bathoorn opened 1 month ago

bathoorn commented 1 month ago

Hi,

I was messing around again and created a v2 geoDesic projection. I starts of with the icosahedron and splits up the triangle in sub triangles. For a v2 a triangle is split up into 4 sub triangles.

Here is an example image

I am currently working on making the projection parametric so geoDesic(2) would create a v2 and geoDesic(3) would create a v3.

If you are interested in adding this I can create a pull request.

bathoorn commented 1 month ago

I also create a snapshot in the test directory which would look like this

geodesic

which I know see is slightly tilted :)

You can use this to create panels for your geodesic dome greenhouse so it looks like a globe from the outside

Fil commented 1 month ago

Fantastic! Can we use a better name, though? I know about https://en.wikipedia.org/wiki/Geodesic_polyhedron and https://en.wikipedia.org/wiki/Geodesic_dome but the "geodesic" term on its own is just the shortest path on the surface.

bathoorn commented 1 month ago

Of course we can. I just needed something to start with. Possibly we can also just add it to the Icosahedron as that is the v1. And then this is the v2. I am still working on making the parent list parametric.

But you can see what it looks like so far here

bathoorn commented 1 month ago

I was looking further into creating different frequency geodesic spheres when trying to split i triangle length in three parts i do the following:

  // icosahedron based class I geodesic sphere
  const polyhedron = [
    [0, 3, 11],
    [0, 5, 3],
    [0, 7, 5],
    [0, 9, 7],
    [0, 11, 9], // North
    [2, 11, 3],
    [3, 4, 2],
    [4, 3, 5],
    [5, 6, 4],
    [6, 5, 7],
    [7, 8, 6],
    [8, 7, 9],
    [9, 10, 8],
    [10, 9, 11],
    [11, 2, 10], // Equator
    [1, 2, 4],
    [1, 4, 6],
    [1, 6, 8],
    [1, 8, 10],
    [1, 10, 2], // South
  ].map((face) => {
    const t = face.map((i) => vertices[i]);
    // create 3 polygons from these using centroid and midpoints
    const a0 = d3.geoInterpolate(t[0], t[1])(0.33333);
    const a1 = d3.geoInterpolate(t[1], t[0])(0.33333);
    const b2 = d3.geoInterpolate(t[1], t[2])(0.33333);
    const b3 = d3.geoInterpolate(t[2], t[1])(0.33333);
    const c4 = d3.geoInterpolate(t[2], t[0])(0.33333);
    const c5 = d3.geoInterpolate(t[0], t[2])(0.33333);
    const m = d3.geoCentroid({
          type: "MultiPoint",
          coordinates: t,
    });
    return [
      [t[0], a0, c5],
      [c5, m, c4], [c5, a0, m], [a0, a1, m],
      [c4, b3, t[2]], [c4, m, b3], [m, b2, b3], [m, a1, b2], [a1, t[1], b2]
    ];
  });

but it looks like it generates some artifacts. Is there a better way to do this?

bathoorn commented 1 month ago

forgot to include an image of the kind of artifacts

image
bathoorn commented 1 month ago

my feeling is that there needs to be a split between 6 and 7 but for some reason there is not.

image

But it is not related to those triangles.

if I change parents from [-1, 2, 0, 2, 5, 1, 5, 3, 7] to [-1, 2, 0, 2, 5, 1, 5, 6, 7] it will look like this

image

Fil commented 3 weeks ago

I think what is happening here is that the triangular faces (in blue) do not correspond to the voronoi cells of their centroids (in red); for instance the top vertex defined for triangle 8 is closer to the centroid of triangle 3.

untitled (26) untitled (27)

In fact I don't think we can use the voronoi projection for this geometry, and must instead construct the corresponding polyhedral projection.

(images generated at https://observablehq.com/d/5a8c5a187d98fa01)

bathoorn commented 3 weeks ago

ah thanks for these images and all your help in getting me to understand how this works.

so why this worked in the other polyhedrons and not here is because in a geodesic sphere not all triangles are the same shape. That is why the voronoi can run into issues determining the edges. As the distance from the center will not always produce the right face now. This will work if all faces are equal shapes.

I will switch it over to the polyhedral projection.

bathoorn commented 3 weeks ago

currently voronoi uses this function to determine which face a coordinate is on

  function find0(lambda, phi) {
    let d0 = Infinity;
    let found = -1;
    for (let i = 0; i < faces.length; i++) {
      const d = geoDistance(faces[i].site, [lambda, phi]);
      if (d < d0) {
        d0 = d;
        found = i;
      }
    }
    return found;
  }

i will do some homework to figure out how to do the calculations for a geodesic sphere.

have a feeling this might help me get there pentaDome_070206.pdf

Fil commented 3 weeks ago

Exactly: find0 is a (relatively slow) search for the closest center, which only serves geometries that are a voronoi tesselation of the sphere (hence the name of the voronoi projection helper). The most generic method is to run through all faces and return that which "polygonContains" the point. It might be a bit slower, but probably OK.

bathoorn commented 3 weeks ago

i will first as quick and dirty try that using this method

even odd rule

bathoorn commented 3 weeks ago

lol here they call it the fill-rule but i read it as the Fil-rule fill-rule

bathoorn commented 3 weeks ago

i am trying this

  function myfind(lambda, phi) {
    let found = -1;
    let faces = polyhedron.flat().map((face) => [...face, face[0]]);
    for (let i = 0; i < faces.length; i++) {
      if (d3.polygonContains(faces[i], [lambda, phi])) {
        found = i;
      }
    }
    return found;
  }

and feeding it into the projection like this

d3.geoPolyhedralVoronoi()
   .polygons(polygons)
   .parents(parents)
   .faceFind(myfind)
   .angle(0)
   .rotate([108,0])
   .scale(131.777)
   .center([162, 0]);

but that results in an error: TypeError: Cannot read properties of undefined (reading 'project')

so i might have to go into d3.geoPolyhedral() all the way

Fil commented 3 weeks ago

Note that d3.polygonContains is for planar polygons, here we need the equivalent for spherical coordinates (d3.geoPolygonContains EDIT: d3.geoContains).

bathoorn commented 3 weeks ago

True and the difference then is that instead of straight lines we are dealing geodesics?

On Wed, 14 Aug 2024, 12:43 Philippe Rivière, @.***> wrote:

Note that d3.polygonContains is for planar polygons, here we need the equivalent for spherical coordinates (d3.geoPolygonContains).

— Reply to this email directly, view it on GitHub https://github.com/d3/d3-geo-polygon/issues/72#issuecomment-2288420758, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAFZWJYMNZTEMZO5CO36YRLZRMYEPAVCNFSM6AAAAABLGW5GTSVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDEOBYGQZDANZVHA . You are receiving this because you authored the thread.Message ID: @.***>

bathoorn commented 3 weeks ago

succes! it is called geoContains and it works for any geoJson object.

  function myfind(lambda, phi) {
    let found = -1;
    let faces = polyhedron.flat().map((face) => ({
        type: "Polygon",
        coordinates: [[...face, face[0]]],
      }));
    for (let i = 0; i < faces.length; i++) {
      if (d3.geoContains(faces[i], [lambda, phi])) {
        found = i;
      }
    }
    return found;
  }

image

Fil commented 3 weeks ago

oops, sorry for the typo! The projection is beautiful!

bathoorn commented 3 weeks ago

Using this I can generate a cover for a geodesic dome for a specific geographical location. This one is for Amsterdam. Then Amsterdam will be at the top point. It does I frequency 3 class I dome.

Later this week I will create a pull request for it. Right now I hacked it into the voronoi by replacing the findface function.