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

Automatically subdivide line elements based on distance to wells (or other vertices) #89

Open
Huite opened this issue Oct 20, 2023 · 0 comments

Comments

@Huite
Copy link
Contributor

Huite commented Oct 20, 2023

A relatively easy way to subdivide long lines based on distance to wells or other element vertices is by:

  1. Projecting all (well) vertices onto a line segment and computing the (perpendicular) distance
  2. Find where the projected point falls. Divide the segment in half if its length is larger than the distance to the vertex.
  3. Repeat until all projections fall in a segment that is smaller than the distance to the vertex.

For example:

  • V1 and V2 are the vertices of the line segment.
  • P1 and P2 are the vertices to two wells
  • The orange and green lines indicate the projection
  • All other markers denote the created subdivision vertices

image

This has some nice properties: we don't have to introduce any logic when projections are close to each other, or when a vertex is far away:

image

image

def _halve_segments(vertices, segments, index):
    """Halve segments at indices provided by `index`."""
    halved = segments[index] * 0.5
    return np.insert(vertices, index, vertices[index] - halved)


def bisect(vertices, projections, distances):
    """
    Iteratively half a line segment, defined by vertices, until the line
    segments are smaller than the provided distances. The result is such that
    every projected point falls within a segment that is shorter than the
    distance to the point.
    
    Parameters
    ----------
    vertices: np.array of floats
        Location of the vertices, expressed as length along the line.
    projections: np.array of floats
        Location of the projected points, expressed as length along the line.
    distances: np.array of floats
        Distance of the points to the line.
    
    Returns
    -------
    refined_vertices: np.array of floats
        Location of new vertices along line.
    """
    def find_split(vertices, projections, distances):
        index = np.searchsorted(vertices, projections)
        segments = np.diff(vertices, prepend=0.0)
        to_split = segments[index] > distances
        return np.unique(index[to_split]), segments, to_split.any()
    
    index, segments, to_split = find_split(vertices, projections, distances)
    while to_split:
        vertices = _halve_segments(vertices, segments, index)
        index, segments, to_split = find_split(vertices, projections, distances)
    return vertices

However, just like in mesh generation with quadtrees or octrees, the line segments are not always "balanced" (a 2:1 ratio at max between neigbhors), see the first segment with the second:

image

Fortunately, this is not difficult to implement in 1D. I'm not sure if it's worthwhile...

def balance(vertices):
    """
    Iteratively half line segments until each segment is at most twice as big
    as a neighboring segment.

    Parameters
    ----------
    vertices: np.array of floats
        Location of the vertices, expressed as length along the line.

    Returns
    -------
    refined_vertices: np.array of floats
        Location of new vertices along line.
    """
    def find_split(vertices):
        segments = np.diff(vertices)
        left = segments[:-1]
        right = segments[1:]
        to_split = np.zeros(segments, dtype=bool)
        # 0.49 should be sufficient to avoid floating point roundoff
        to_split[:-1] = (0.49 * left) > right
        to_split[1:] = (0.49 * right) > left
        index = np.argwhere(to_split) + 1  # TODO: check
        return index, segments, to_split.any()
 
    index, segments, to_split = find_split(vertices)
    while to_split:
        vertices = _halve_segments(vertices, segments, index)
        index, segments, to_split = find_split(vertices)
    return vertices

Some other thoughts:

  • I think it makes sense to define a "refine" method on the model object, because it should only be done after all elements are added to the model. Then:

  • A collection of all vertices can be made.

  • Project these on every line segment and compute distance.

  • Split every segment with the logic above.

  • Replace the element in the model by one with the additional vertices inserted (?)

  • It's also possible to refine and "balance" a linestring in its entirety by expressing the vertices as distance measured along the linestring.

  • Maybe (only) somewhat useful when two segments have very obtuse angles with each other?

  • There might be an indexing trick to get this to work when the geometry forms a ring (e.g. numpy take has a "wrap" mode).

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

No branches or pull requests

2 participants