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

Improve subsetting robustness by using triangle for buffer meshing #110

Merged
merged 26 commits into from
Sep 25, 2023
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
9ae6a6d
Use triangle to improve subsetting transition
SorooshMani-NOAA Sep 13, 2023
19506ab
Use shapely2 funcs & add new shp2msht func
SorooshMani-NOAA Sep 14, 2023
b0771b4
Fix CRS issue in new merge
SorooshMani-NOAA Sep 14, 2023
91fb3c9
Fix issue related to separate multipolygon subset buffer regions
SorooshMani-NOAA Sep 14, 2023
138f6c1
Cleanup and remove shapely2 func
SorooshMani-NOAA Sep 14, 2023
4e674e7
Code reorg & cleanup
SorooshMani-NOAA Sep 14, 2023
69b8556
Bugfix for cleanup!
SorooshMani-NOAA Sep 14, 2023
0d1eb80
Pin python version to below 3.11 for triangle support
SorooshMani-NOAA Sep 14, 2023
44fbabf
Fix tests for the now keyword only args to util func
SorooshMani-NOAA Sep 14, 2023
446c0ad
Add empty placeholder test - RED
SorooshMani-NOAA Sep 14, 2023
7904b61
Add tests and more sanity checks to code
SorooshMani-NOAA Sep 15, 2023
186e5e2
Add tests for triangulation
SorooshMani-NOAA Sep 15, 2023
132e114
Fix triangulation and mesh poly calc
SorooshMani-NOAA Sep 21, 2023
6dbd530
Bugfix for new mesh polygon calc
SorooshMani-NOAA Sep 21, 2023
c80221e
Fix subsetting logic for bufferzone meshing
Sep 21, 2023
cc0b82a
Fix getting aux point XY for triangulation
SorooshMani-NOAA Sep 22, 2023
eed2a70
Revert python ver restriction due to triangle pkg
SorooshMani-NOAA Sep 25, 2023
575c993
Fix order of bbox points for hfun calc
SorooshMani-NOAA Sep 25, 2023
23a7e90
Fix mamba argument for py ver
SorooshMani-NOAA Sep 25, 2023
1dfffeb
Fix for pylint
SorooshMani-NOAA Sep 25, 2023
be406b9
Fix triangulate aux point input processing
SorooshMani-NOAA Sep 25, 2023
62bc573
Remove numpy deprecated func
SorooshMani-NOAA Sep 25, 2023
ab9e99d
Update threshold for size from mesh func
SorooshMani-NOAA Sep 25, 2023
42995a7
Fix for checking index ring equivalence
SorooshMani-NOAA Sep 25, 2023
21ccc38
Test for values of msht_from_numpy
SorooshMani-NOAA Sep 25, 2023
e000811
Add test for mesh polygon return type
SorooshMani-NOAA Sep 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
environment-file: ./environment.yml
environment-name: ocsmesh-env
extra-specs: |
python=3.*
python=3.10
- name: Install dependencies
shell: bash -l {0}
run: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/functional_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
python-version: [ '3.9', '3.10', '3.11' ]
python-version: [ '3.9', '3.10' ]

steps:
- uses: actions/checkout@v3
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/functional_test_2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
fail-fast: false
matrix:
os: [ ubuntu-latest ]
python-version: [ '3.9', '3.10', '3.11' ]
python-version: [ '3.9', '3.10' ]

steps:
- name: Checkout
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/pylint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ jobs:
environment-file: ./environment.yml
environment-name: ocsmesh-env
extra-specs: |
python=3.*
python=3.10
- name: Install dependencies
shell: bash -l {0}
run: |
Expand Down
178 changes: 99 additions & 79 deletions ocsmesh/cli/subset_n_combine.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,12 @@
from jigsawpy.jig_t import jigsaw_jig_t
import numpy as np
from pyproj import CRS, Transformer
from scipy.interpolate import griddata
from scipy.spatial import cKDTree
from shapely.geometry import MultiPolygon, Polygon, GeometryCollection
from shapely.ops import polygonize, unary_union
from shapely.ops import polygonize, unary_union, transform

from ocsmesh import Raster, Geom, Mesh, Hfun, utils
from ocsmesh import Raster, Geom, Mesh, Hfun, utils, JigsawDriver

logging.basicConfig(
stream=sys.stdout,
Expand Down Expand Up @@ -242,96 +243,111 @@ def _add_overlap_to_polygon(self, mesh, polygon):

return new_polygon, clipped_mesh

def _calculate_mesh_size_function(self, hires_mesh_clip, lores_mesh_clip, crs):
def _calculate_mesh_size_function(
self,
buffer_domain,
hires_mesh_clip,
lores_mesh_clip,
buffer_crs
):

# calculate mesh size for clipped bits
hfun_hires = Hfun(Mesh(deepcopy(hires_mesh_clip)))
hfun_hires.size_from_mesh()
hfun_lowres = Hfun(Mesh(deepcopy(lores_mesh_clip)))
hfun_lowres.size_from_mesh()
assert(buffer_crs == hires_mesh_clip.crs == lores_mesh_clip.crs)

# _logger.info("Writing hfun clips...")
# start = time()
# hfun_hires.mesh.write(str(out_dir / "hfun_fine.2dm"), format="2dm", overwrite=True)
# hfun_lowres.mesh.write(str(out_dir / "hfun_coarse.2dm"), format="2dm", overwrite=True)
# _logger.info(f"Done in {time() - start} sec")
# HARDCODED FOR NOW
approx_elem_per_width = 3

utm = utils.estimate_bounds_utm(
buffer_domain.bounds, buffer_crs
)
transformer = Transformer.from_crs(buffer_crs, utm, always_xy=True)
buffer_domain = transform(transformer.transform, buffer_domain)

jig_hfun = utils.merge_msh_t(
hfun_hires.msh_t(), hfun_lowres.msh_t(),
out_crs=crs)#jig_geom) ### TODO: CRS MUST BE == GEOM_MSHT CRS
# calculate mesh size for clipped bits
msht_hi = deepcopy(hires_mesh_clip)
utils.reproject(msht_hi, utm)
hfun_hi = Hfun(Mesh(msht_hi))
hfun_hi.size_from_mesh()

msht_lo = deepcopy(lores_mesh_clip)
utils.reproject(msht_lo, utm)
hfun_lo = Hfun(Mesh(msht_lo))
hfun_lo.size_from_mesh()

msht_cdt = utils.triangulate_polygon(
buffer_domain, None, opts='p'
)
msht_cdt.crs = utm

return jig_hfun
# utils.reproject(msht_cdt, utm)
hfun_cdt = Hfun(Mesh(msht_cdt))
hfun_cdt.size_from_mesh()

hfun_cdt_sz = deepcopy(hfun_cdt.msh_t().value) / approx_elem_per_width
msht_cdt.value[:] = hfun_cdt_sz
hfun_rep = Hfun(Mesh(msht_cdt))

def _generate_mesh_for_buffer_region(
self, buffer_polygon, jig_hfun, crs):
mesh_domain_rep = JigsawDriver(
geom=Geom(buffer_domain, crs=utm),
hfun=hfun_rep,
initial_mesh=False
).run(sieve=0)

seam = Geom(buffer_polygon, crs=crs)
msht_domain_rep = deepcopy(mesh_domain_rep.msh_t)
utils.reproject(msht_domain_rep, utm)

jig_geom = seam.msh_t()
pts_2mesh = np.vstack(
(hfun_hi.msh_t().vert2['coord'], hfun_lo.msh_t().vert2['coord'])
)
val_2mesh = np.vstack(
(hfun_hi.msh_t().value, hfun_lo.msh_t().value)
)
domain_sz_1 = griddata(
pts_2mesh, val_2mesh, msht_domain_rep.vert2['coord'], method='linear'
)
domain_sz_2 = griddata(
pts_2mesh, val_2mesh, msht_domain_rep.vert2['coord'], method='nearest'
)
domain_sz = domain_sz_1.copy()
domain_sz[np.isnan(domain_sz_1)] = domain_sz_2[np.isnan(domain_sz_1)]

# IMPORTANT: Setting these to -1 results in NON CONFORMAL boundary
# jig_geom.vert2['IDtag'][:] = -1
# jig_geom.edge2['IDtag'][:] = -1
msht_domain_rep.value[:] = domain_sz
hfun_interp = Hfun(Mesh(msht_domain_rep))

jig_init = deepcopy(seam.msh_t())
jig_init.vert2['IDtag'][:] = -1
jig_init.edge2['IDtag'][:] = -1
return hfun_interp

# Calculate length of all edges on geom
geom_edges = jig_geom.edge2['index']
geom_coords = jig_geom.vert2['coord'][geom_edges, :]
geom_edg_lens = np.sqrt(np.sum(
np.power(np.abs(np.diff(geom_coords, axis=1)), 2),
axis=2)).squeeze()

# TODO: Use marche to calculate mesh size in the area between
# the two regions?
def _generate_mesh_for_buffer_region(
self, buffer_polygon, hfun_buffer, buffer_crs):

_logger.info("Meshing...")
start = time()
opts = jigsaw_jig_t()
opts.hfun_scal = "absolute"
opts.hfun_hmin = np.min(geom_edg_lens)
opts.hfun_hmax = np.max(geom_edg_lens)
# opts.hfun_hmin = np.min(jig_hfun.value.ravel())
# opts.hfun_hmax = np.max(jig_hfun.value.ravel())
opts.optm_zip_ = False
opts.optm_div_ = False
opts.mesh_dims = +2
opts.mesh_rad2 = 1.05
# opts.mesh_rad2 = 2.0

jig_mesh = jigsaw_msh_t()
jig_mesh.mshID = 'euclidean-mesh'
jig_mesh.ndims = 2
jig_mesh.crs = jig_geom.crs

jigsawpy.lib.jigsaw(
opts, jig_geom, jig_mesh,
# hfun=jig_hfun,
init=jig_init
)

jig_mesh.value = np.zeros((jig_mesh.vert2.shape[0], 1))
self._transform_mesh(jig_mesh, crs)

# NOTE: Remove out of domain elements (some corner cases!)
elems = jig_mesh.tria3['index']
coord = jig_mesh.vert2['coord']

gdf_elems = gpd.GeoDataFrame(
geometry=[Polygon(tri) for tri in coord[elems]],
crs=jig_mesh.crs
utm = utils.estimate_bounds_utm(
buffer_polygon.bounds, buffer_crs
)
idx = gdf_elems.representative_point().sindex.query(
seam.get_multipolygon(), predicate='intersects'
transformer = Transformer.from_crs(buffer_crs, utm, always_xy=True)
buffer_polygon = transform(transformer.transform, buffer_polygon)

mesh_domain_interp = JigsawDriver(
geom=Geom(buffer_polygon, crs=utm),
hfun=hfun_buffer,
initial_mesh=False
).run(sieve=0)

msht_domain_interp = deepcopy(mesh_domain_interp.msh_t)
in_verts_mask = np.ones_like(msht_domain_interp.vert2, dtype=bool)
in_verts_mask[
np.unique(utils.get_boundary_edges(msht_domain_interp))
] = False

# utils.reproject(msht_domain_interp, buffer_crs)
msht_buffer = utils.triangulate_polygon(
shape=buffer_polygon,
aux_pts=msht_domain_interp.vert2[in_verts_mask]['coord'],
opts='p'
)
flag = np.zeros(len(gdf_elems), dtype=bool)
flag[idx] = True
jig_mesh.tria3 = jig_mesh.tria3[flag]
msht_buffer.crs = utm

utils.reproject(msht_buffer, buffer_crs)

return jig_mesh
return msht_buffer


def _transform_mesh(self, mesh, out_crs):
Expand Down Expand Up @@ -623,9 +639,13 @@ def _main(

poly_seam = poly_seam_8

# TODO: Get CRS correctly from geom utm
# jig_hfun = self._calculate_mesh_size_function(jig_clip_hires, jig_clip_lowres, crs)
jig_buffer_mesh = self._generate_mesh_for_buffer_region(poly_seam, None, crs)
hfun_buffer = self._calculate_mesh_size_function(
poly_seam, jig_clip_hires, jig_clip_lowres, crs
)
jig_buffer_mesh = self._generate_mesh_for_buffer_region(
poly_seam, hfun_buffer, crs
)
# TODO: UTM TO CRS?

_logger.info("Combining meshes...")
start = time()
Expand Down
42 changes: 4 additions & 38 deletions ocsmesh/geom/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,42 +144,8 @@ def multipolygon_to_jigsaw_msh_t(
transformer = Transformer.from_crs(crs, utm_crs, always_xy=True)
multipolygon = ops.transform(transformer.transform, multipolygon)

vert2: List[Tuple[Tuple[float, float], int]] = []
for polygon in multipolygon.geoms:
if np.all(
np.asarray(
polygon.exterior.coords).flatten() == float('inf')):
raise NotImplementedError("ellispoidal-mesh")
for x, y in polygon.exterior.coords[:-1]:
vert2.append(((x, y), 0))
for interior in polygon.interiors:
for x, y in interior.coords[:-1]:
vert2.append(((x, y), 0))

# edge2
edge2: List[Tuple[int, int]] = []
for polygon in multipolygon.geoms:
polygon = [polygon.exterior, *polygon.interiors]
for linear_ring in polygon:
_edge2 = []
for i in range(len(linear_ring.coords)-2):
_edge2.append((i, i+1))
_edge2.append((_edge2[-1][1], _edge2[0][0]))
edge2.extend(
[(e0+len(edge2), e1+len(edge2))
for e0, e1 in _edge2])
# geom
geom = jigsaw_msh_t()
geom.ndims = +2
geom.mshID = 'euclidean-mesh'
# TODO: Consider ellipsoidal case.
# geom.mshID = 'euclidean-mesh' if self._ellipsoid is None \
# else 'ellipsoidal-mesh'
geom.vert2 = np.asarray(vert2, dtype=jigsaw_msh_t.VERT2_t)
geom.edge2 = np.asarray(
[((e0, e1), 0) for e0, e1 in edge2],
dtype=jigsaw_msh_t.EDGE2_t)
geom.crs = crs
msht = utils.shape_to_msh_t(multipolygon)
msht.crs = crs
if utm_crs is not None:
geom.crs = utm_crs
return geom
msht.crs = utm_crs
return msht
Loading