potree module¶
Potree point cloud viewer widget implementation.
Potree is loaded via CDN since it's a complex Three.js-based viewer. This implementation provides a Python wrapper for point cloud visualization.
PotreeViewer (MapWidget)
¶
Interactive point cloud viewer using Potree.
Potree is a WebGL-based point cloud renderer for large-scale LiDAR datasets. This class provides a Python interface for loading and visualizing point clouds.
Note: Potree is loaded from CDN due to its complex Three.js dependencies.
Examples:
>>> from anymap_ts import PotreeViewer
>>> viewer = PotreeViewer()
>>> viewer.load_point_cloud("path/to/pointcloud/cloud.js")
>>> viewer
Source code in anymap_ts/potree.py
class PotreeViewer(MapWidget):
"""Interactive point cloud viewer using Potree.
Potree is a WebGL-based point cloud renderer for large-scale LiDAR
datasets. This class provides a Python interface for loading and
visualizing point clouds.
Note: Potree is loaded from CDN due to its complex Three.js dependencies.
Example:
>>> from anymap_ts import PotreeViewer
>>> viewer = PotreeViewer()
>>> viewer.load_point_cloud("path/to/pointcloud/cloud.js")
>>> viewer
"""
# ESM module for frontend
_esm = STATIC_DIR / "potree.js"
# Potree-specific traits
point_budget = traitlets.Int(1000000).tag(sync=True)
point_size = traitlets.Float(1.0).tag(sync=True)
fov = traitlets.Float(60.0).tag(sync=True)
background = traitlets.Unicode("#000000").tag(sync=True)
# EDL (Eye Dome Lighting) settings
edl_enabled = traitlets.Bool(True).tag(sync=True)
edl_radius = traitlets.Float(1.4).tag(sync=True)
edl_strength = traitlets.Float(0.4).tag(sync=True)
# Point clouds
point_clouds = traitlets.Dict({}).tag(sync=True)
# Camera
camera_position = traitlets.List([0, 0, 100]).tag(sync=True)
camera_target = traitlets.List([0, 0, 0]).tag(sync=True)
def __init__(
self,
width: str = "100%",
height: str = "600px",
point_budget: int = 1000000,
point_size: float = 1.0,
fov: float = 60.0,
background: str = "#000000",
edl_enabled: bool = True,
**kwargs,
):
"""Initialize a Potree viewer.
Args:
width: Widget width as CSS string.
height: Widget height as CSS string.
point_budget: Maximum number of points to render.
point_size: Default point size.
fov: Field of view in degrees.
background: Background color (hex string).
edl_enabled: Enable Eye Dome Lighting.
**kwargs: Additional widget arguments.
"""
# Potree doesn't use center/zoom like maps
super().__init__(
center=[0, 0],
zoom=1,
width=width,
height=height,
point_budget=point_budget,
point_size=point_size,
fov=fov,
background=background,
edl_enabled=edl_enabled,
**kwargs,
)
self.point_clouds = {}
# -------------------------------------------------------------------------
# Point Cloud Methods
# -------------------------------------------------------------------------
def load_point_cloud(
self,
url: str,
name: Optional[str] = None,
visible: bool = True,
point_size: Optional[float] = None,
point_size_type: str = "adaptive",
shape: str = "circle",
color: Optional[str] = None,
**kwargs,
) -> None:
"""Load a point cloud.
Args:
url: URL to point cloud (Potree format or LAZ/LAS via Entwine).
name: Point cloud name.
visible: Whether point cloud is visible.
point_size: Point size (overrides default).
point_size_type: 'fixed', 'attenuated', or 'adaptive'.
shape: Point shape ('square', 'circle', 'paraboloid').
color: Point color (hex string or None for native colors).
**kwargs: Additional material options.
"""
cloud_id = name or f"pointcloud_{len(self.point_clouds)}"
self.point_clouds = {
**self.point_clouds,
cloud_id: {
"url": url,
"name": cloud_id,
"visible": visible,
"material": {
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
},
}
self.call_js_method(
"loadPointCloud",
url=url,
name=cloud_id,
visible=visible,
material={
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
)
def remove_point_cloud(self, name: str) -> None:
"""Remove a point cloud.
Args:
name: Point cloud name to remove.
"""
if name in self.point_clouds:
clouds = dict(self.point_clouds)
del clouds[name]
self.point_clouds = clouds
self.call_js_method("removePointCloud", name=name)
def set_point_cloud_visibility(self, name: str, visible: bool) -> None:
"""Set point cloud visibility.
Args:
name: Point cloud name.
visible: Whether to show the point cloud.
"""
self.call_js_method("setPointCloudVisibility", name=name, visible=visible)
# -------------------------------------------------------------------------
# Camera Methods
# -------------------------------------------------------------------------
def set_camera_position(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera position.
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_position = [x, y, z]
self.call_js_method("setCameraPosition", x=x, y=y, z=z)
def set_camera_target(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera target (look-at point).
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_target = [x, y, z]
self.call_js_method("setCameraTarget", x=x, y=y, z=z)
def fly_to_point_cloud(self, name: Optional[str] = None) -> None:
"""Fly to a point cloud or all point clouds.
Args:
name: Point cloud name (None for all).
"""
self.call_js_method("flyToPointCloud", name=name)
def reset_camera(self) -> None:
"""Reset camera to default view."""
self.call_js_method("resetCamera")
# -------------------------------------------------------------------------
# Visualization Settings
# -------------------------------------------------------------------------
def set_point_budget(self, budget: int) -> None:
"""Set the point budget (max points to render).
Args:
budget: Maximum number of points.
"""
self.point_budget = budget
self.call_js_method("setPointBudget", budget=budget)
def set_point_size(self, size: float) -> None:
"""Set default point size.
Args:
size: Point size.
"""
self.point_size = size
self.call_js_method("setPointSize", size=size)
def set_fov(self, fov: float) -> None:
"""Set field of view.
Args:
fov: Field of view in degrees.
"""
self.fov = fov
self.call_js_method("setFOV", fov=fov)
def set_background(self, color: str) -> None:
"""Set background color.
Args:
color: Background color (hex string).
"""
self.background = color
self.call_js_method("setBackground", color=color)
def set_edl(
self,
enabled: bool = True,
radius: float = 1.4,
strength: float = 0.4,
) -> None:
"""Configure Eye Dome Lighting.
Args:
enabled: Whether to enable EDL.
radius: EDL radius.
strength: EDL strength.
"""
self.edl_enabled = enabled
self.edl_radius = radius
self.edl_strength = strength
self.call_js_method(
"setEDL",
enabled=enabled,
radius=radius,
strength=strength,
)
# -------------------------------------------------------------------------
# Measurement Tools
# -------------------------------------------------------------------------
def add_measurement_tool(self, tool_type: str = "distance") -> None:
"""Add a measurement tool.
Args:
tool_type: Type of measurement ('point', 'distance', 'area', 'angle', 'height', 'profile').
"""
self.call_js_method("addMeasurementTool", type=tool_type)
def clear_measurements(self) -> None:
"""Clear all measurements."""
self.call_js_method("clearMeasurements")
# -------------------------------------------------------------------------
# Clipping
# -------------------------------------------------------------------------
def add_clipping_volume(
self,
volume_type: str = "box",
position: Optional[Tuple[float, float, float]] = None,
scale: Optional[Tuple[float, float, float]] = None,
) -> None:
"""Add a clipping volume.
Args:
volume_type: Type of volume ('box', 'polygon', 'plane').
position: Volume position (x, y, z).
scale: Volume scale (x, y, z).
"""
self.call_js_method(
"addClippingVolume",
type=volume_type,
position=list(position) if position else None,
scale=list(scale) if scale else None,
)
def clear_clipping_volumes(self) -> None:
"""Clear all clipping volumes."""
self.call_js_method("clearClippingVolumes")
# -------------------------------------------------------------------------
# Annotations
# -------------------------------------------------------------------------
def add_annotation(
self,
position: Tuple[float, float, float],
title: str,
description: str = "",
camera_position: Optional[Tuple[float, float, float]] = None,
camera_target: Optional[Tuple[float, float, float]] = None,
) -> None:
"""Add an annotation.
Args:
position: Annotation position (x, y, z).
title: Annotation title.
description: Annotation description.
camera_position: Camera position when focused.
camera_target: Camera target when focused.
"""
self.call_js_method(
"addAnnotation",
position=list(position),
title=title,
description=description,
cameraPosition=list(camera_position) if camera_position else None,
cameraTarget=list(camera_target) if camera_target else None,
)
def clear_annotations(self) -> None:
"""Clear all annotations."""
self.call_js_method("clearAnnotations")
# -------------------------------------------------------------------------
# HTML Export
# -------------------------------------------------------------------------
def _generate_html_template(self) -> str:
"""Generate standalone HTML for Potree viewer."""
template_path = Path(__file__).parent / "templates" / "potree.html"
if template_path.exists():
template = template_path.read_text(encoding="utf-8")
else:
template = self._get_default_template()
state = {
"point_budget": self.point_budget,
"point_size": self.point_size,
"fov": self.fov,
"background": self.background,
"edl_enabled": self.edl_enabled,
"edl_radius": self.edl_radius,
"edl_strength": self.edl_strength,
"point_clouds": self.point_clouds,
"camera_position": self.camera_position,
"camera_target": self.camera_target,
"width": self.width,
"height": self.height,
"js_calls": self._js_calls,
}
template = template.replace("{{state}}", json.dumps(state, indent=2))
return template
def _get_default_template(self) -> str:
"""Get default HTML template."""
return """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Potree Viewer</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; overflow: hidden; }
#potree_render_area { position: absolute; top: 0; bottom: 0; width: 100%; }
</style>
</head>
<body>
<div id="potree_render_area"></div>
<script>
const state = {{state}};
document.getElementById('potree_render_area').innerHTML = '<p style="color: white; padding: 20px;">Potree viewer requires Potree library. Point clouds: ' + Object.keys(state.point_clouds || {}).length + '</p>';
</script>
</body>
</html>"""
__init__(self, width='100%', height='600px', point_budget=1000000, point_size=1.0, fov=60.0, background='#000000', edl_enabled=True, **kwargs)
special
¶
Initialize a Potree viewer.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
width |
str |
Widget width as CSS string. |
'100%' |
height |
str |
Widget height as CSS string. |
'600px' |
point_budget |
int |
Maximum number of points to render. |
1000000 |
point_size |
float |
Default point size. |
1.0 |
fov |
float |
Field of view in degrees. |
60.0 |
background |
str |
Background color (hex string). |
'#000000' |
edl_enabled |
bool |
Enable Eye Dome Lighting. |
True |
**kwargs |
Additional widget arguments. |
{} |
Source code in anymap_ts/potree.py
def __init__(
self,
width: str = "100%",
height: str = "600px",
point_budget: int = 1000000,
point_size: float = 1.0,
fov: float = 60.0,
background: str = "#000000",
edl_enabled: bool = True,
**kwargs,
):
"""Initialize a Potree viewer.
Args:
width: Widget width as CSS string.
height: Widget height as CSS string.
point_budget: Maximum number of points to render.
point_size: Default point size.
fov: Field of view in degrees.
background: Background color (hex string).
edl_enabled: Enable Eye Dome Lighting.
**kwargs: Additional widget arguments.
"""
# Potree doesn't use center/zoom like maps
super().__init__(
center=[0, 0],
zoom=1,
width=width,
height=height,
point_budget=point_budget,
point_size=point_size,
fov=fov,
background=background,
edl_enabled=edl_enabled,
**kwargs,
)
self.point_clouds = {}
add_annotation(self, position, title, description='', camera_position=None, camera_target=None)
¶
Add an annotation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
position |
Tuple[float, float, float] |
Annotation position (x, y, z). |
required |
title |
str |
Annotation title. |
required |
description |
str |
Annotation description. |
'' |
camera_position |
Optional[Tuple[float, float, float]] |
Camera position when focused. |
None |
camera_target |
Optional[Tuple[float, float, float]] |
Camera target when focused. |
None |
Source code in anymap_ts/potree.py
def add_annotation(
self,
position: Tuple[float, float, float],
title: str,
description: str = "",
camera_position: Optional[Tuple[float, float, float]] = None,
camera_target: Optional[Tuple[float, float, float]] = None,
) -> None:
"""Add an annotation.
Args:
position: Annotation position (x, y, z).
title: Annotation title.
description: Annotation description.
camera_position: Camera position when focused.
camera_target: Camera target when focused.
"""
self.call_js_method(
"addAnnotation",
position=list(position),
title=title,
description=description,
cameraPosition=list(camera_position) if camera_position else None,
cameraTarget=list(camera_target) if camera_target else None,
)
add_clipping_volume(self, volume_type='box', position=None, scale=None)
¶
Add a clipping volume.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
volume_type |
str |
Type of volume ('box', 'polygon', 'plane'). |
'box' |
position |
Optional[Tuple[float, float, float]] |
Volume position (x, y, z). |
None |
scale |
Optional[Tuple[float, float, float]] |
Volume scale (x, y, z). |
None |
Source code in anymap_ts/potree.py
def add_clipping_volume(
self,
volume_type: str = "box",
position: Optional[Tuple[float, float, float]] = None,
scale: Optional[Tuple[float, float, float]] = None,
) -> None:
"""Add a clipping volume.
Args:
volume_type: Type of volume ('box', 'polygon', 'plane').
position: Volume position (x, y, z).
scale: Volume scale (x, y, z).
"""
self.call_js_method(
"addClippingVolume",
type=volume_type,
position=list(position) if position else None,
scale=list(scale) if scale else None,
)
add_measurement_tool(self, tool_type='distance')
¶
Add a measurement tool.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tool_type |
str |
Type of measurement ('point', 'distance', 'area', 'angle', 'height', 'profile'). |
'distance' |
Source code in anymap_ts/potree.py
def add_measurement_tool(self, tool_type: str = "distance") -> None:
"""Add a measurement tool.
Args:
tool_type: Type of measurement ('point', 'distance', 'area', 'angle', 'height', 'profile').
"""
self.call_js_method("addMeasurementTool", type=tool_type)
clear_annotations(self)
¶
Clear all annotations.
Source code in anymap_ts/potree.py
def clear_annotations(self) -> None:
"""Clear all annotations."""
self.call_js_method("clearAnnotations")
clear_clipping_volumes(self)
¶
Clear all clipping volumes.
Source code in anymap_ts/potree.py
def clear_clipping_volumes(self) -> None:
"""Clear all clipping volumes."""
self.call_js_method("clearClippingVolumes")
clear_measurements(self)
¶
Clear all measurements.
Source code in anymap_ts/potree.py
def clear_measurements(self) -> None:
"""Clear all measurements."""
self.call_js_method("clearMeasurements")
fly_to_point_cloud(self, name=None)
¶
Fly to a point cloud or all point clouds.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
Optional[str] |
Point cloud name (None for all). |
None |
Source code in anymap_ts/potree.py
def fly_to_point_cloud(self, name: Optional[str] = None) -> None:
"""Fly to a point cloud or all point clouds.
Args:
name: Point cloud name (None for all).
"""
self.call_js_method("flyToPointCloud", name=name)
load_point_cloud(self, url, name=None, visible=True, point_size=None, point_size_type='adaptive', shape='circle', color=None, **kwargs)
¶
Load a point cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
url |
str |
URL to point cloud (Potree format or LAZ/LAS via Entwine). |
required |
name |
Optional[str] |
Point cloud name. |
None |
visible |
bool |
Whether point cloud is visible. |
True |
point_size |
Optional[float] |
Point size (overrides default). |
None |
point_size_type |
str |
'fixed', 'attenuated', or 'adaptive'. |
'adaptive' |
shape |
str |
Point shape ('square', 'circle', 'paraboloid'). |
'circle' |
color |
Optional[str] |
Point color (hex string or None for native colors). |
None |
**kwargs |
Additional material options. |
{} |
Source code in anymap_ts/potree.py
def load_point_cloud(
self,
url: str,
name: Optional[str] = None,
visible: bool = True,
point_size: Optional[float] = None,
point_size_type: str = "adaptive",
shape: str = "circle",
color: Optional[str] = None,
**kwargs,
) -> None:
"""Load a point cloud.
Args:
url: URL to point cloud (Potree format or LAZ/LAS via Entwine).
name: Point cloud name.
visible: Whether point cloud is visible.
point_size: Point size (overrides default).
point_size_type: 'fixed', 'attenuated', or 'adaptive'.
shape: Point shape ('square', 'circle', 'paraboloid').
color: Point color (hex string or None for native colors).
**kwargs: Additional material options.
"""
cloud_id = name or f"pointcloud_{len(self.point_clouds)}"
self.point_clouds = {
**self.point_clouds,
cloud_id: {
"url": url,
"name": cloud_id,
"visible": visible,
"material": {
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
},
}
self.call_js_method(
"loadPointCloud",
url=url,
name=cloud_id,
visible=visible,
material={
"size": point_size or self.point_size,
"pointSizeType": point_size_type,
"shape": shape,
"color": color,
**kwargs,
},
)
remove_point_cloud(self, name)
¶
Remove a point cloud.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Point cloud name to remove. |
required |
Source code in anymap_ts/potree.py
def remove_point_cloud(self, name: str) -> None:
"""Remove a point cloud.
Args:
name: Point cloud name to remove.
"""
if name in self.point_clouds:
clouds = dict(self.point_clouds)
del clouds[name]
self.point_clouds = clouds
self.call_js_method("removePointCloud", name=name)
reset_camera(self)
¶
Reset camera to default view.
Source code in anymap_ts/potree.py
def reset_camera(self) -> None:
"""Reset camera to default view."""
self.call_js_method("resetCamera")
set_background(self, color)
¶
Set background color.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
color |
str |
Background color (hex string). |
required |
Source code in anymap_ts/potree.py
def set_background(self, color: str) -> None:
"""Set background color.
Args:
color: Background color (hex string).
"""
self.background = color
self.call_js_method("setBackground", color=color)
set_camera_position(self, x, y, z)
¶
Set camera position.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x |
float |
X coordinate. |
required |
y |
float |
Y coordinate. |
required |
z |
float |
Z coordinate. |
required |
Source code in anymap_ts/potree.py
def set_camera_position(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera position.
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_position = [x, y, z]
self.call_js_method("setCameraPosition", x=x, y=y, z=z)
set_camera_target(self, x, y, z)
¶
Set camera target (look-at point).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x |
float |
X coordinate. |
required |
y |
float |
Y coordinate. |
required |
z |
float |
Z coordinate. |
required |
Source code in anymap_ts/potree.py
def set_camera_target(
self,
x: float,
y: float,
z: float,
) -> None:
"""Set camera target (look-at point).
Args:
x: X coordinate.
y: Y coordinate.
z: Z coordinate.
"""
self.camera_target = [x, y, z]
self.call_js_method("setCameraTarget", x=x, y=y, z=z)
set_edl(self, enabled=True, radius=1.4, strength=0.4)
¶
Configure Eye Dome Lighting.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
enabled |
bool |
Whether to enable EDL. |
True |
radius |
float |
EDL radius. |
1.4 |
strength |
float |
EDL strength. |
0.4 |
Source code in anymap_ts/potree.py
def set_edl(
self,
enabled: bool = True,
radius: float = 1.4,
strength: float = 0.4,
) -> None:
"""Configure Eye Dome Lighting.
Args:
enabled: Whether to enable EDL.
radius: EDL radius.
strength: EDL strength.
"""
self.edl_enabled = enabled
self.edl_radius = radius
self.edl_strength = strength
self.call_js_method(
"setEDL",
enabled=enabled,
radius=radius,
strength=strength,
)
set_fov(self, fov)
¶
Set field of view.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
fov |
float |
Field of view in degrees. |
required |
Source code in anymap_ts/potree.py
def set_fov(self, fov: float) -> None:
"""Set field of view.
Args:
fov: Field of view in degrees.
"""
self.fov = fov
self.call_js_method("setFOV", fov=fov)
set_point_budget(self, budget)
¶
Set the point budget (max points to render).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
budget |
int |
Maximum number of points. |
required |
Source code in anymap_ts/potree.py
def set_point_budget(self, budget: int) -> None:
"""Set the point budget (max points to render).
Args:
budget: Maximum number of points.
"""
self.point_budget = budget
self.call_js_method("setPointBudget", budget=budget)
set_point_cloud_visibility(self, name, visible)
¶
Set point cloud visibility.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name |
str |
Point cloud name. |
required |
visible |
bool |
Whether to show the point cloud. |
required |
Source code in anymap_ts/potree.py
def set_point_cloud_visibility(self, name: str, visible: bool) -> None:
"""Set point cloud visibility.
Args:
name: Point cloud name.
visible: Whether to show the point cloud.
"""
self.call_js_method("setPointCloudVisibility", name=name, visible=visible)
set_point_size(self, size)
¶
Set default point size.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
size |
float |
Point size. |
required |
Source code in anymap_ts/potree.py
def set_point_size(self, size: float) -> None:
"""Set default point size.
Args:
size: Point size.
"""
self.point_size = size
self.call_js_method("setPointSize", size=size)