Skip to content

Commit 9f140f7

Browse files
authored
Add some vector functions (opengeos#582)
* Add some vector functions * Replace Stamen.Terrain with Esri.WorldTopoMap
1 parent 4ab1407 commit 9f140f7

13 files changed

+228
-12
lines changed

docs/notebooks/11_linked_maps.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"metadata": {},
8787
"outputs": [],
8888
"source": [
89-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
89+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
9090
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
9191
]
9292
},

docs/notebooks/40_plotly_gui.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"metadata": {},
5454
"outputs": [],
5555
"source": [
56-
"m.add_basemap(\"Stamen.Terrain\")"
56+
"m.add_basemap(\"Esri.WorldTopoMap\")"
5757
]
5858
},
5959
{

docs/workshops/FOSS4G_2021.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2176,7 +2176,7 @@
21762176
"metadata": {},
21772177
"outputs": [],
21782178
"source": [
2179-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
2179+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
21802180
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
21812181
]
21822182
},

docs/workshops/SIGSPATIAL_2021.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1963,7 +1963,7 @@
19631963
"metadata": {},
19641964
"outputs": [],
19651965
"source": [
1966-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
1966+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
19671967
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
19681968
]
19691969
},

docs/workshops/YouthMappers_2021.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2129,7 +2129,7 @@
21292129
"metadata": {},
21302130
"outputs": [],
21312131
"source": [
2132-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
2132+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
21332133
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
21342134
]
21352135
},

examples/notebooks/11_linked_maps.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
"metadata": {},
8787
"outputs": [],
8888
"source": [
89-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
89+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
9090
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
9191
]
9292
},

examples/notebooks/40_plotly_gui.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
"metadata": {},
5454
"outputs": [],
5555
"source": [
56-
"m.add_basemap(\"Stamen.Terrain\")"
56+
"m.add_basemap(\"Esri.WorldTopoMap\")"
5757
]
5858
},
5959
{

examples/workshops/FOSS4G_2021.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2176,7 +2176,7 @@
21762176
"metadata": {},
21772177
"outputs": [],
21782178
"source": [
2179-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
2179+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
21802180
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
21812181
]
21822182
},

examples/workshops/SIGSPATIAL_2021.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -1963,7 +1963,7 @@
19631963
"metadata": {},
19641964
"outputs": [],
19651965
"source": [
1966-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
1966+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
19671967
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
19681968
]
19691969
},

examples/workshops/YouthMappers_2021.ipynb

+1-1
Original file line numberDiff line numberDiff line change
@@ -2129,7 +2129,7 @@
21292129
"metadata": {},
21302130
"outputs": [],
21312131
"source": [
2132-
"layers = ['Stamen.Terrain', 'OpenTopoMap']\n",
2132+
"layers = ['Esri.WorldTopoMap', 'OpenTopoMap']\n",
21332133
"leafmap.linked_maps(rows=1, cols=2, height='400px', layers=layers)"
21342134
]
21352135
},

leafmap/common.py

+216
Original file line numberDiff line numberDiff line change
@@ -11247,3 +11247,219 @@ def pmtiles_metadata(input_file: str) -> Dict[str, Union[str, int, List[str]]]:
1124711247
metadata["center"] = header["center"]
1124811248
metadata["bounds"] = header["bounds"]
1124911249
return metadata
11250+
11251+
11252+
def raster_to_vector(source, output, simplify_tolerance=None, dst_crs=None, open_args={}, **kwargs):
11253+
"""Vectorize a raster dataset.
11254+
11255+
Args:
11256+
source (str): The path to the tiff file.
11257+
output (str): The path to the vector file.
11258+
simplify_tolerance (float, optional): The maximum allowed geometry displacement.
11259+
The higher this value, the smaller the number of vertices in the resulting geometry.
11260+
"""
11261+
import rasterio
11262+
import shapely
11263+
import geopandas as gpd
11264+
from rasterio import features
11265+
11266+
with rasterio.open(source, **open_args) as src:
11267+
band = src.read()
11268+
11269+
mask = band != 0
11270+
shapes = features.shapes(band, mask=mask, transform=src.transform)
11271+
11272+
fc = [
11273+
{"geometry": shapely.geometry.shape(shape), "properties": {"value": value}}
11274+
for shape, value in shapes
11275+
]
11276+
if simplify_tolerance is not None:
11277+
for i in fc:
11278+
i["geometry"] = i["geometry"].simplify(tolerance=simplify_tolerance)
11279+
11280+
gdf = gpd.GeoDataFrame.from_features(fc)
11281+
if src.crs is not None:
11282+
gdf.set_crs(crs=src.crs, inplace=True)
11283+
11284+
if dst_crs is not None:
11285+
gdf = gdf.to_crs(dst_crs)
11286+
11287+
gdf.to_file(output, **kwargs)
11288+
11289+
11290+
def overlay_images(
11291+
image1,
11292+
image2,
11293+
alpha=0.5,
11294+
backend="TkAgg",
11295+
height_ratios=[10, 1],
11296+
show_args1={},
11297+
show_args2={},
11298+
):
11299+
"""Overlays two images using a slider to control the opacity of the top image.
11300+
11301+
Args:
11302+
image1 (str | np.ndarray): The first input image at the bottom represented as a NumPy array or the path to the image.
11303+
image2 (_type_): The second input image on top represented as a NumPy array or the path to the image.
11304+
alpha (float, optional): The alpha value of the top image. Defaults to 0.5.
11305+
backend (str, optional): The backend of the matplotlib plot. Defaults to "TkAgg".
11306+
height_ratios (list, optional): The height ratios of the two subplots. Defaults to [10, 1].
11307+
show_args1 (dict, optional): The keyword arguments to pass to the imshow() function for the first image. Defaults to {}.
11308+
show_args2 (dict, optional): The keyword arguments to pass to the imshow() function for the second image. Defaults to {}.
11309+
11310+
"""
11311+
import sys
11312+
import matplotlib
11313+
import matplotlib.pyplot as plt
11314+
import matplotlib.widgets as mpwidgets
11315+
11316+
if "google.colab" in sys.modules:
11317+
backend = "inline"
11318+
print(
11319+
"The TkAgg backend is not supported in Google Colab. The overlay_images function will not work on Colab."
11320+
)
11321+
return
11322+
11323+
matplotlib.use(backend)
11324+
11325+
if isinstance(image1, str):
11326+
if image1.startswith("http"):
11327+
image1 = download_file(image1)
11328+
11329+
if not os.path.exists(image1):
11330+
raise ValueError(f"Input path {image1} does not exist.")
11331+
11332+
if isinstance(image2, str):
11333+
if image2.startswith("http"):
11334+
image2 = download_file(image2)
11335+
11336+
if not os.path.exists(image2):
11337+
raise ValueError(f"Input path {image2} does not exist.")
11338+
11339+
# Load the two images
11340+
x = plt.imread(image1)
11341+
y = plt.imread(image2)
11342+
11343+
# Create the plot
11344+
fig, (ax0, ax1) = plt.subplots(2, 1, gridspec_kw={"height_ratios": height_ratios})
11345+
img0 = ax0.imshow(x, **show_args1)
11346+
img1 = ax0.imshow(y, alpha=alpha, **show_args2)
11347+
11348+
# Define the update function
11349+
def update(value):
11350+
img1.set_alpha(value)
11351+
fig.canvas.draw_idle()
11352+
11353+
# Create the slider
11354+
slider0 = mpwidgets.Slider(ax=ax1, label="alpha", valmin=0, valmax=1, valinit=alpha)
11355+
slider0.on_changed(update)
11356+
11357+
# Display the plot
11358+
plt.show()
11359+
11360+
11361+
def blend_images(
11362+
img1,
11363+
img2,
11364+
alpha=0.5,
11365+
output=False,
11366+
show=True,
11367+
figsize=(12, 10),
11368+
axis="off",
11369+
**kwargs,
11370+
):
11371+
"""
11372+
Blends two images together using the addWeighted function from the OpenCV library.
11373+
11374+
Args:
11375+
img1 (numpy.ndarray): The first input image on top represented as a NumPy array.
11376+
img2 (numpy.ndarray): The second input image at the bottom represented as a NumPy array.
11377+
alpha (float): The weighting factor for the first image in the blend. By default, this is set to 0.5.
11378+
output (str, optional): The path to the output image. Defaults to False.
11379+
show (bool, optional): Whether to display the blended image. Defaults to True.
11380+
figsize (tuple, optional): The size of the figure. Defaults to (12, 10).
11381+
axis (str, optional): The axis of the figure. Defaults to "off".
11382+
**kwargs: Additional keyword arguments to pass to the cv2.addWeighted() function.
11383+
11384+
Returns:
11385+
numpy.ndarray: The blended image as a NumPy array.
11386+
"""
11387+
import cv2
11388+
import numpy as np
11389+
import matplotlib.pyplot as plt
11390+
11391+
# Resize the images to have the same dimensions
11392+
if isinstance(img1, str):
11393+
if img1.startswith("http"):
11394+
img1 = download_file(img1)
11395+
11396+
if not os.path.exists(img1):
11397+
raise ValueError(f"Input path {img1} does not exist.")
11398+
11399+
img1 = cv2.imread(img1)
11400+
11401+
if isinstance(img2, str):
11402+
if img2.startswith("http"):
11403+
img2 = download_file(img2)
11404+
11405+
if not os.path.exists(img2):
11406+
raise ValueError(f"Input path {img2} does not exist.")
11407+
11408+
img2 = cv2.imread(img2)
11409+
11410+
if img1.dtype == np.float32:
11411+
img1 = (img1 * 255).astype(np.uint8)
11412+
11413+
if img2.dtype == np.float32:
11414+
img2 = (img2 * 255).astype(np.uint8)
11415+
11416+
if img1.dtype != img2.dtype:
11417+
img2 = img2.astype(img1.dtype)
11418+
11419+
img1 = cv2.resize(img1, (img2.shape[1], img2.shape[0]))
11420+
11421+
# Blend the images using the addWeighted function
11422+
beta = 1 - alpha
11423+
blend_img = cv2.addWeighted(img1, alpha, img2, beta, 0, **kwargs)
11424+
11425+
if output:
11426+
array_to_image(blend_img, output, img2)
11427+
11428+
if show:
11429+
plt.figure(figsize=figsize)
11430+
plt.imshow(blend_img)
11431+
plt.axis(axis)
11432+
plt.show()
11433+
else:
11434+
return blend_img
11435+
11436+
11437+
def regularize(source, output=None, crs="EPSG:4326", **kwargs):
11438+
"""Regularize a polygon GeoDataFrame.
11439+
11440+
Args:
11441+
source (str | gpd.GeoDataFrame): The input file path or a GeoDataFrame.
11442+
output (str, optional): The output file path. Defaults to None.
11443+
11444+
11445+
Returns:
11446+
gpd.GeoDataFrame: The output GeoDataFrame.
11447+
"""
11448+
import geopandas as gpd
11449+
11450+
if isinstance(source, str):
11451+
gdf = gpd.read_file(source)
11452+
elif isinstance(source, gpd.GeoDataFrame):
11453+
gdf = source
11454+
else:
11455+
raise ValueError("The input source must be a GeoDataFrame or a file path.")
11456+
11457+
polygons = gdf.geometry.apply(lambda geom: geom.minimum_rotated_rectangle)
11458+
result = gpd.GeoDataFrame(geometry=polygons, data=gdf.drop("geometry", axis=1))
11459+
11460+
if crs is not None:
11461+
result.to_crs(crs, inplace=True)
11462+
if output is not None:
11463+
result.to_file(output, **kwargs)
11464+
else:
11465+
return result

leafmap/plotlymap.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -659,7 +659,7 @@ def add_heatmap_demo(self, **kwargs):
659659
**kwargs,
660660
)
661661

662-
self.add_basemap("Stamen.Terrain")
662+
self.add_basemap("Esri.WorldTopoMap")
663663
self.add_trace(heatmap)
664664

665665
def add_gdf_demo(

leafmap/toolbar.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -3241,7 +3241,7 @@ def plotly_basemap_gui(canvas, map_min_width="78%", map_max_width="98%"):
32413241

32423242
map_widget.layout.width = map_min_width
32433243

3244-
value = "Stamen.Terrain"
3244+
value = "Esri.WorldTopoMap"
32453245
m.add_basemap(value)
32463246

32473247
dropdown = widgets.Dropdown(

0 commit comments

Comments
 (0)