Pijay
Pijay

Reputation: 53

Check if points is in 3D Box by coordinates in pandas

I have the coordinates of 3D box (oriented in the same referential and with coordinates min and max on XYZ) in the dataframe BOX And coordinate XYZ of a points in dataframe PTS

BOX = pd.DataFrame({'Box':['BA', 'BB', 'BC'], 'Box-Xmin':[1, 2, 30], 'Box-Ymin':[1,2,30], 'Box-Zmin':[1,2,30], 'Box-Xmax':[2,4,60], 'Box-Ymax':[2,4,60], 'Box-Zmax':[2,4,60]})
PTS = pd.DataFrame({'Point':['P1', 'P2', 'P3'], 'Pt-X':[0, 45, 20], 'Pt-Y':[8,33,40], 'Pt-Z':[5,30,50]})

I try to make a merge, join between this both dataframe when the point is in the BOX like:

Box Point
BA 
BB 
BC  P2
BC  P3

I have check on geo pandas side but it's more for 2D coordinates.

Thanks :)

Upvotes: 1

Views: 458

Answers (2)

Dev Khadka
Dev Khadka

Reputation: 5451

you can also do it with numpy as below, not sure if it performs better but I think it should

BOX = pd.DataFrame({'Box':['BA', 'BB', 'BC'], 'Box-Xmin':[1, 2, 30], 'Box-Ymin':[1,2,30], 'Box-Zmin':[1,2,30], 'Box-Xmax':[50,4,60], 'Box-Ymax':[50,4,60], 'Box-Zmax':[50,4,60]})
PTS = pd.DataFrame({'Point':['P1', 'P2', 'P3'], 'Pt-X':[7, 45, 40], 'Pt-Y':[8,33,40], 'Pt-Z':[5,30,60]})


l_box = BOX[["Box-Xmin","Box-Ymin","Box-Zmin"]].values
u_box = BOX[["Box-Xmax","Box-Ymax","Box-Zmax"]].values

## add new dimension to use broadcast to compare all pairs of box and points
pts = PTS[["Pt-X","Pt-Y","Pt-Z"]].values[:, np.newaxis, :]

# this is convenient expression but the block below will use less memory
# cond = np.all((l_box<= pts) & (pts<=u_box), axis=2)

cond1 = np.greater_equal(pts, l_box)
cond2 = np.greater_equal(u_box, pts)
np.logical_and(cond1, cond2, out=cond1)
cond2 = None
cond = np.all(cond1, axis=2)

# fancy index for points falling inside box
pts_ind = np.arange(len(pts)).reshape(-1,1).repeat(np.sum(cond, axis=1))

# fancy index of box having points
box_ind = np.arange(len(l_box)).repeat(np.sum(cond, axis=0))

# finally create dataframe using the numpy array
pd.DataFrame(dict(Box=BOX["Box"].values[box_ind], 
Point=PTS["Point"].values[pts_ind]))

Result

**Input**
Box Box-Xmin    Box-Ymin    Box-Zmin    Box-Xmax    Box-Ymax    Box-Zmax
0   BA  1   1   1   50  50  50
1   BB  2   2   2   4   4   4
2   BC  30  30  30  60  60  60


Point   Pt-X    Pt-Y    Pt-Z
0   P1  7   8   5
1   P2  45  33  30
2   P3  40  40  60

**Result**
    Box Point
0   BA  P1
1   BA  P2
2   BC  P2
3   BC  P3

Upvotes: 1

Quang Hoang
Quang Hoang

Reputation: 150745

If you data are not too big, you can use broadcasting:

# for convenience
bmins = BOX.filter(like='min').values
bmaxs = BOX.filter(like='max').values
coords = PTS.filter(like='Pt').values

xy = np.nonzero(((coords[:,None, :] >= bmins) &  # compare each coordinate with min
                 (coords[:,None, :] <= bmaxs)    # compare each coordinate with max
                ).all(-1)                       # make sure all comparisons are True
               )                                # nonzero collects only True values

new_df = pd.DataFrame({'Box':BOX.loc[xy[1],'Box'].values,
                       'Point':PTS.loc[xy[0],'Point'].values})

Output:

  Box Point
0  BC    P2
1  BC    P3

which you can merge to BOX for your desired output, e.g:

BOX[['Box']].merge(new_df, on='Box', how='left')

gives:

  Box Point
0  BA   NaN
1  BB   NaN
2  BC    P2
3  BC    P3

Upvotes: 2

Related Questions