create separated regions from polydata

The vtkPolyDataEdgeConnectivityFilter works fine, it is crashing because it needs polygons, not just lines.
Here is a solution in vedo, (uncomment reverse() to have it clockwise):

from vedo import *
m = Mesh("poly1.vtk").clean().triangulate()
ms = m.split(must_share_edge=True) # return a list of meshes
s1 = ms[0].boundaries().join_segments()[0]#.reverse()
print("First polyline", s1.points())
show(ms, s1, s1.labels2d(), axes=1)
First polyline 
[[ 1562187.9 -5830366.   2038637.4]
 [ 1764714.6 -5772294.   2038637.4]
 [ 1965086.  -5707191.5  2038637.4]
 [ 2163063.2 -5635136.   2038637.4]
 [ 2358410.5 -5556212.5  2038637.4]
 [ 2331017.8 -5491532.5  2235859.8]
 [ 2137944.8 -5569539.   2235859.8]
 [ 1942269.2 -5640759.   2235859.8]
 [ 1744224.8 -5705107.5  2235859.8]
 [ 1544058.  -5762504.5  2235859.8]]

Screenshot from 2023-03-28 14-28-58

pip install -U git+https://github.com/marcomusy/vedo.git

This is what I meant by some oddity in the dataset causing the crash. Hence, I believe @PBrockmann could join the lines with coincident vertexes to form a single polyline and then process it with vtkPolyDataEdgeConnectivityFilter : vtkStripper not joining contiguous lines to form a single vtkPolyLine.

May I ask you how you “join the lines with coincident vertexes to form a single polyline” ?
With vtk python API.

@marcomusy : thank you very much for this real test. I didn’t know vedo. I would try to stay witk vtk/pyvista.

Well, port it. It’s not difficult. The name of the classes and methods are much like the same.

For example:

   vtkSmartPointer<vtkPolyData> poly = vtkSmartPointer<vtkPolyData>::New();
    poly->SetPoints( points );
    poly->SetLines( segments );
    poly->GetCellData()->SetScalars( dataIndexes );

Ports to:

   poly = vtk.vtkPolyData()
   poly.SetPoints( points )
   poly.SetLines( segments )
   poly.GetCellData().SetScalars( dataIndexes )

I can reproduce what the vedo code from @marcomusy has proposed with the VTK API.
But the split function from the vedo code gives anyway an uncorrect number of regions.

I have prepared 3 vtk files to play with.
https://thredds-su.ipsl.fr/thredds/fileServer/ipsl_thredds/brocksce/tmp/poly_11.vtk
image

https://thredds-su.ipsl.fr/thredds/fileServer/ipsl_thredds/brocksce/tmp/poly_07.vtk
image

https://thredds-su.ipsl.fr/thredds/fileServer/ipsl_thredds/brocksce/tmp/poly_03.vtk
image

In the poly_11.vtk, I expect to find 2 regions.
In the poly_07.vtk, 3 regions.
In the poly_03.vtk, 1 region.

from vedo import *
m = Mesh("poly_07.vtk").clean().triangulate()
ms = m.split(must_share_edge=True) # return a list of meshes
print(len(ms))

Gives 1 instead of 3.

Here is the solution I can proposed.

Described in this answer as well.

Notice that all the different self intersected polygons are separated from their duplicate points into multiple polygons.

image

import pyvista as pv
import vtk
import random
import numpy as np

! wget -nc https://thredds-su.ipsl.fr/thredds/fileServer/ipsl_thredds/brocksce/tmp/mesh3D_03.vtk
mesh = pv.PolyData('mesh3D_03.vtk')

edges = mesh.extract_feature_edges(boundary_edges=True)

pl = pv.Plotter()

R = 6371E3
pl.add_mesh(pv.Sphere(radius=R*0.999, theta_resolution=360, phi_resolution=180))
pl.add_mesh(mesh, show_edges=True, edge_color="gray")

regions = edges.connectivity()
regCount = len(set(regions.get_array("RegionId")))

connectivityFilter = vtk.vtkPolyDataConnectivityFilter()
stripper = vtk.vtkStripper()

allPolys = []
for r in range(regCount):
    connectivityFilter.SetInputData(edges)
    connectivityFilter.SetExtractionModeToSpecifiedRegions()
    connectivityFilter.InitializeSpecifiedRegionList()
    connectivityFilter.AddSpecifiedRegion(r)
    connectivityFilter.Update()

    stripper.SetInputData(connectivityFilter.GetOutput())
    stripper.SetJoinContiguousSegments(True)
    stripper.Update() 
    
    cleaner = vtk.vtkCleanPolyData()
    cleaner.SetInputData(stripper.GetOutput())
    cleaner.Update()
    
    reg = cleaner.GetOutput()
    print(r, reg.GetNumberOfCells(), reg.GetNumberOfPoints())
    
    #==============================
    polys = []
    for cellIndex in range(reg.GetNumberOfCells()):

        c = reg.GetCell(cellIndex)
        print("  Cell #%d Number of points: %d"  %(cellIndex, c.GetNumberOfPoints()))
    
        d = c.GetPointIds()

        ids = []
        for idIndex in range(d.GetNumberOfIds()):
            ids.append(d.GetId(idIndex))

        unique, count = np.unique(ids, return_counts=True)
        dup = unique[count > 1]
    
        for id in dup[::-1]:
            select = np.where(ids == id)[0]
            polys.append(ids[select[0]:select[1]+1])
            idsIndices = list(range(select[0],select[1]))
            ids = list(np.delete(ids, idsIndices))
        
    print("  ", len(polys))
    for p in polys:
        points = []
        for id in p:
            points.append(reg.GetPoint(id))
        m1 = pv.MultipleLines(points)
        print("  add poly")
        allPolys.append(points)
      
        random_color = "#"+''.join([random.choice('0123456789ABCDEF') for i in range(6)])
        pl.add_mesh(m1, color=random_color, line_width=4)

viewer = pl.show(jupyter_backend='trame', return_viewer=True)
display(viewer)

Then my intention was to produce maps with filled polygons in various projections.
have a look to

The polygon file polys_03.pickle can be produced from

import shapely
from pyproj import Transformer

#=======================================================
# From a cartesian 3D CS (geocentric) to a global latitude/longitude CS
trans_GPS_to_XYZ = Transformer.from_crs(4978, 4326, always_xy=True)

#=======================================================
# Define a function to make continuous the polygon
# because the projection transformation produces output
# between -180 and 180 

def makePolygonContinuous(arrayLonLat):
    for i in range(1,len(arrayLonLat)):
        lon1 = arrayLonLat[i-1][0]
        lon2 = arrayLonLat[i][0]
        if (abs(lon2-360-lon1) < abs(lon2-lon1)):
            arrayLonLat[i][0] = lon2-360
        elif (abs(lon2+360-lon1) < abs(lon2-lon1)):
            arrayLonLat[i][0] = lon2+360
        
    return arrayLonLat

#=======================================================
allPolys2 = []
for polyIndex in range(len(allPolys)):
    print(polyIndex, len(allPolys[polyIndex]))
    points = []
    for point in allPolys[polyIndex]:              
        x,y,z = point
        lon, lat, Z = trans_GPS_to_XYZ.transform(x,y,z)
        points.append([lon, lat])

    points = makePolygonContinuous(points)
        
    polygonShapely = shapely.Polygon(points)
    polygonShapelyOriented = shapely.geometry.polygon.orient(polygonShapely)
    
    allPolys2.append(polygonShapelyOriented)

#=======================================================
import pickle
pickle.dump(allPolys2, open("polys_03.pickle","wb"))

Thank you for the different help messages received.

2 Likes