A relatively easy way to subdivide long lines based on distance to wells or other element vertices is by:
- Projecting all (well) vertices onto a line segment and computing the (perpendicular) distance
- Find where the projected point falls. Divide the segment in half if its length is larger than the distance to the vertex.
- 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

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:


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:

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).
A relatively easy way to subdivide long lines based on distance to wells or other element vertices is by:
For example:
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:
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:
Fortunately, this is not difficult to implement in 1D. I'm not sure if it's worthwhile...
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).