-
install using pip
pip install git+https://github.com/kasra-hosseini/geotree.git
-
install geotree from the source code:
- Clone geotree source code:
git clone https://github.com/kasra-hosseini/geotree.git
- Install geotree:
cd /path/to/my/geotree pip install -v -e .
Alternatively:
cd /path/to/my/geotree python setup.py install
Instantiate gtree:
from geotree import gtree
import matplotlib.pyplot as plt
import numpy as np
mytree = gtree()
Define the first set of points or base
:
npoints = 200
lons = np.random.randint(-180, 180, npoints)
lats = np.random.randint(-90, 90, npoints)
depths = np.zeros(npoints)
Add lons/lats/depths of the first set of points:
mytree.add_lonlatdep(lons=lons,
lats=lats,
depths=depths)
add_lonlatdep
.
For KDTree, these values are converted into x/y/z (Cartesian coordinate system) internally (and in meters).
If you already have x/y/z in meters, you can use add_xyz
function.
Define queries:
q_npoints = 10
q_lons = np.random.randint(-150, 150, q_npoints)
q_lats = np.random.randint(-70, 70, q_npoints)
q_depths = np.zeros(q_npoints)
Add lons/lats/depths of queries:
mytree.add_lonlatdep_query(lons=q_lons,
lats=q_lats,
depths=q_depths)
add_lonlatdep_query
.
For KDTree, these values are converted into x/y/z (Cartesian coordinate system) internally (and in meters).
If you already have x/y/z in meters, you can use add_xyz_q
function.
Create KDTree (kdt):
mytree.create_kdt()
Choose the desired number of neighbors (and upper bound for distance, if needed):
mytree.query_kdt(num_neighs=3, distance_upper=np.inf)
Now, for each query, distances to the closest base
neighbors and their indices are stored in (row-wise):
# distances to the closest `base` neighbors
mytree.dists2query
# indices of the closest `base` neighbors
mytree.indxs2query
The results are shown in the following figure.
The base
points are shown in black dots and the queries are shown by X
.
The closest three base
neighbors are connected to each query.
Same results but on a interrupted Goode homolosine projection:
Create Ball tree:
mytree.create_balltree()
Choose the desired number of neighbors:
mytree.query_balltree(num_neighs=3)
Now, for each query, distances to the closest base
neighbors and their indices are stored in (row-wise):
# distances to the closest `base` neighbors
mytree.dists2query
# indices of the closest `base` neighbors
mytree.indxs2query
Here, we have a set of points, labelled as base
in the figure below, with some values (i.e., colors of those points).
We also have another set of points, queries
in the figure, for which we want to compute values using the values of base
.
Geotree
uses the following algorithm for this task:
- it creates a tree (KDTree or BallTree) for
base
points. - for a
query
point, it finds the closest neighbors frombase
(the number of neighbors is specified by the user, in the figure below, this number was 4). - it assigns a value to the
query
point by computing the weighted average of the values of the neighboringbase
points. The weights are proportional to the inverse distance.
(See more results below)
Instantiate gtree:
from geotree import gtree
import numpy as np
mytree = gtree()
Define the first set of points or base
:
npoints = 100
lons = np.random.randint(-180, 180, npoints)
lats = np.random.randint(-90, 90, npoints)
depths = np.zeros(npoints)
# assign some values (to each point)
vals = np.zeros(npoints)
vals[:(npoints//2)] = 0
vals[(npoints//2):] = 1
Add lons/lats/depths of the first set of points:
mytree.add_lonlatdep(lons=lons,
lats=lats,
depths=depths)
add_lonlatdep
.
For KDTree, these values are converted into x/y/z (Cartesian coordinate system) internally (and in meters).
If you already have x/y/z in meters, you can use add_xyz
function
Define queries:
q_npoints = 10000
q_lons = np.random.randint(-180, 180, q_npoints)
q_lats = np.random.randint(-90, 90, q_npoints)
q_depths = np.zeros(q_npoints)
Add lons/lats/depths of queries:
mytree.add_lonlatdep_query(lons=q_lons,
lats=q_lats,
depths=q_depths)
add_lonlatdep_query
.
For KDTree, these values are converted into x/y/z (Cartesian coordinate system) internally (and in meters).
If you already have x/y/z in meters, you can use add_xyz_q
function.
Assign values to the first set of points: (note: size of vals should be the same as the first set of points)
mytree.add_vals(vals)
Interpolation: compute the values of queries
from the values of base
points:
As the first example, we consider one neighbor (i.e., only the value of the closest base
point to a query is used)
mytree.interpolate(num_neighs=1, method="kdtree")
mytree.interpolate(num_neighs=2, method="kdtree")
Or on a interrupted Goode homolosine projection:
mytree.interpolate(num_neighs=10, method="kdtree")
In the above examples, we used KDTree, we can change the method to Ball tree
by simply:
mytree.interpolate(num_neighs=2, method="balltree")
To plot the above figures:
import matplotlib.pyplot as plt
plt.figure(figsize=(15, 7))
plt.scatter(q_lons, q_lats,
c=mytree.interp_vals,
marker="x",
vmin=min(vals), vmax=max(vals),
label="queries")
plt.scatter(lons, lats,
c=vals,
marker="o",
vmin=min(vals), vmax=max(vals), edgecolors="r",
label="base",
zorder=100)
plt.legend(bbox_to_anchor=(0., 1.01, 1., .05),
loc="right", ncol=2,
fontsize=16,
borderaxespad=0.)
plt.title(f"Method: {mytree.interp_method}", size=16)
plt.colorbar()
plt.grid()
plt.tight_layout()
plt.show()
geotree
can read lon/lat/depth or x/y/z as inputs. Here is a list of relevant functions:
add_lonlatdep
(depth should be in meters; positive depths specify points inside the Earth.)add_lonlatdep_query
(same as above except for queries)add_xyz
(in meters)add_xyz_q
(for queries, in meters)
In this section, we show two functions in geotree: lonlatdep2xyz_spherical
and xyz2lonlatdep_spherical
.
These are used internally to convert between lon/lat/dep and x/y/z.
from geotree import convert as geoconvert
import matplotlib.pyplot as plt
import numpy as np
Define a set of lons/lats/depths:
npoints = 100
lons = np.random.randint(-180, 180, npoints)
lats = np.random.randint(-90, 90, npoints)
depths = np.zeros(npoints)
Here, we use geoconvert.lonlatdep2xyz_spherical
to convert lons/lats/depths ---> x/y/z (in meters)
x, y, z = geoconvert.lonlatdep2xyz_spherical(lons,
lats,
depths,
return_one_arr=False)
In the figure:
- Left pabel: in geographic coordinate
- Right panel: x/y/z in meters (on a sphere with radius of 6371000m)
fig = plt.figure(figsize=(12, 5))
plt.subplot(1, 2, 1)
plt.scatter(lons, lats,
c="k",
marker="o",
zorder=100)
plt.xlabel("lons", size=20)
plt.ylabel("lats", size=20)
plt.xticks(size=14); plt.yticks(size=14)
plt.xlim(-180, 180); plt.ylim(-90, 90)
plt.grid()
# ---
ax = fig.add_subplot(1, 2, 2, projection='3d')
ax.scatter3D(x, y, z, c="k", marker="o");
ax.set_xlabel('X (m)', size=16)
ax.set_ylabel('Y (m)', size=16)
ax.set_zlabel('Z (m)', size=16)
plt.tight_layout()
plt.show()
Just as test, we now use geoconvert.xyz2lonlatdep_spherical
to convert x/y/z back to lons/lats/depths:
lons_conv, lats_conv, depths_conv = geoconvert.xyz2lonlatdep_spherical(x, y, z,
return_one_arr=False)
and, we measure the L1 error between original lons/lats/depths and the ones computed above:
print(max(abs(lons - lons_conv)))
print(max(abs(lats - lats_conv)))
print(max(abs(depths - depths_conv)))
Outputs:
2.842170943040401e-14
2.842170943040401e-14
9.313225746154785e-10
The figure compares KDTree and Ball tree for build (left) and query (right) times. The left panel shows the build time as a function of number of points used in the tree. The build times of the two methods are very similar. In the right panel, we first constructed a tree for one million points and then measured the time to query this tree with different number of queries (x-axis).
See this Jupyter notebook for details.