Skip to content

Commit e655269

Browse files
authored
PolygonLayer (#330)
It looks like this is now working! <img width="686" alt="image" src="https://github.com/developmentseed/lonboard/assets/15164633/91edc23d-0ecd-49c0-bf1e-0068a617a8fb"> This does have the caveat that picking currently only works for _polygons_ and not their associated exteriors. See geoarrow/deck.gl-layers#114 Todo: - [x] Use PolygonLayer as the default polygon rendering in `viz` - [x] Render stroke by default in `viz`. - [ ] Get input from designers on good default colors for the Polygon exterior - [x] Document difference between the two polygon layers - [x] Use published @geoarrow/deck.gl-layers beta, instead of local install. Closes #197 ----- Old: It turns out that there are still some issues on the JS side of the PolygonLayer. I think I had only tested it with Polygon, not MultiPolygon, input, which is why I didn't catch these two. More info in geoarrow/deck.gl-layers#102
1 parent 2bed40c commit e655269

File tree

11 files changed

+467
-56
lines changed

11 files changed

+467
-56
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ gdf = gpd.GeoDataFrame(...)
5050
viz(gdf)
5151
```
5252

53-
Under the hood, this delegates to a [`ScatterplotLayer`](https://developmentseed.org/lonboard/latest/api/layers/scatterplot-layer/), [`PathLayer`](https://developmentseed.org/lonboard/latest/api/layers/path-layer/), or [`SolidPolygonLayer`](https://developmentseed.org/lonboard/latest/api/layers/solid-polygon-layer/). Refer to the [documentation](https://developmentseed.org/lonboard/) and [examples](https://developmentseed.org/lonboard/latest/examples/internet-speeds/) for more control over rendering.
53+
Under the hood, this delegates to a [`ScatterplotLayer`](https://developmentseed.org/lonboard/latest/api/layers/scatterplot-layer/), [`PathLayer`](https://developmentseed.org/lonboard/latest/api/layers/path-layer/), or [`PolygonLayer`](https://developmentseed.org/lonboard/latest/api/layers/polygon-layer/). Refer to the [documentation](https://developmentseed.org/lonboard/) and [examples](https://developmentseed.org/lonboard/latest/examples/internet-speeds/) for more control over rendering.
5454

5555
## Documentation
5656

docs/api/layers/polygon-layer.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# PolygonLayer
2+
3+
::: lonboard.PolygonLayer
4+
options:
5+
inherited_members: true

lonboard/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
HeatmapLayer,
1212
PathLayer,
1313
PointCloudLayer,
14+
PolygonLayer,
1415
ScatterplotLayer,
1516
SolidPolygonLayer,
1617
)

lonboard/_layer.py

Lines changed: 257 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
HeatmapLayerKwargs,
4848
PathLayerKwargs,
4949
PointCloudLayerKwargs,
50+
PolygonLayerKwargs,
5051
ScatterplotLayerKwargs,
5152
SolidPolygonLayerKwargs,
5253
)
@@ -606,6 +607,249 @@ def __init__(self, **kwargs: BitmapTileLayerKwargs):
606607
"""
607608

608609

610+
class PolygonLayer(BaseArrowLayer):
611+
"""The `PolygonLayer` renders filled, stroked and/or extruded polygons.
612+
613+
!!! note
614+
615+
This layer is essentially a combination of a [`PathLayer`][lonboard.PathLayer]
616+
and a [`SolidPolygonLayer`][lonboard.SolidPolygonLayer]. This has some overhead
617+
beyond a `SolidPolygonLayer`, so if you're looking for the maximum performance
618+
with large data, you may want to use a `SolidPolygonLayer` directly.
619+
620+
**Example:**
621+
622+
From GeoPandas:
623+
624+
```py
625+
import geopandas as gpd
626+
from lonboard import Map, PolygonLayer
627+
628+
# A GeoDataFrame with Polygon or MultiPolygon geometries
629+
gdf = gpd.GeoDataFrame()
630+
layer = PolygonLayer.from_geopandas(
631+
gdf,
632+
get_fill_color=[255, 0, 0],
633+
get_line_color=[0, 100, 100, 150],
634+
)
635+
m = Map(layer)
636+
```
637+
638+
From [geoarrow-rust](https://geoarrow.github.io/geoarrow-rs/python/latest):
639+
640+
```py
641+
from geoarrow.rust.core import read_parquet
642+
from lonboard import Map, PolygonLayer
643+
644+
# Example: A GeoParquet file with Polygon or MultiPolygon geometries
645+
table = read_parquet("path/to/file.parquet")
646+
layer = PolygonLayer(
647+
table=table,
648+
get_fill_color=[255, 0, 0],
649+
get_line_color=[0, 100, 100, 150],
650+
)
651+
m = Map(layer)
652+
```
653+
"""
654+
655+
def __init__(
656+
self,
657+
*,
658+
table: pa.Table,
659+
_rows_per_chunk: Optional[int] = None,
660+
**kwargs: Unpack[PolygonLayerKwargs],
661+
):
662+
super().__init__(table=table, _rows_per_chunk=_rows_per_chunk, **kwargs)
663+
664+
@classmethod
665+
def from_geopandas(
666+
cls,
667+
gdf: gpd.GeoDataFrame,
668+
*,
669+
auto_downcast: bool = True,
670+
**kwargs: Unpack[PolygonLayerKwargs],
671+
) -> Self:
672+
return super().from_geopandas(gdf=gdf, auto_downcast=auto_downcast, **kwargs)
673+
674+
_layer_type = traitlets.Unicode("polygon").tag(sync=True)
675+
676+
table = PyarrowTableTrait(
677+
allowed_geometry_types={EXTENSION_NAME.POLYGON, EXTENSION_NAME.MULTIPOLYGON}
678+
)
679+
"""A GeoArrow table with a Polygon or MultiPolygon column.
680+
681+
This is the fastest way to plot data from an existing GeoArrow source, such as
682+
[geoarrow-rust](https://geoarrow.github.io/geoarrow-rs/python/latest) or
683+
[geoarrow-pyarrow](https://geoarrow.github.io/geoarrow-python/main/index.html).
684+
685+
If you have a GeoPandas `GeoDataFrame`, use
686+
[`from_geopandas`][lonboard.PolygonLayer.from_geopandas] instead.
687+
"""
688+
689+
stroked = traitlets.Bool(None, allow_none=True).tag(sync=True)
690+
"""Whether to draw an outline around the polygon (solid fill).
691+
692+
Note that both the outer polygon as well the outlines of any holes will be drawn.
693+
694+
- Type: `bool`, optional
695+
- Default: `True`
696+
"""
697+
698+
filled = traitlets.Bool(None, allow_none=True).tag(sync=True)
699+
"""Whether to draw a filled polygon (solid fill).
700+
701+
Note that only the area between the outer polygon and any holes will be filled.
702+
703+
- Type: `bool`, optional
704+
- Default: `True`
705+
"""
706+
707+
extruded = traitlets.Bool(None, allow_none=True).tag(sync=True)
708+
"""Whether to extrude the polygons.
709+
710+
Based on the elevations provided by the `getElevation` accessor.
711+
712+
If set to `false`, all polygons will be flat, this generates less geometry and is
713+
faster than simply returning 0 from getElevation.
714+
715+
- Type: `bool`, optional
716+
- Default: `False`
717+
"""
718+
719+
wireframe = traitlets.Bool(None, allow_none=True).tag(sync=True)
720+
"""
721+
Whether to generate a line wireframe of the polygon. The outline will have
722+
"horizontal" lines closing the top and bottom polygons and a vertical line
723+
(a "strut") for each vertex on the polygon.
724+
725+
- Type: `bool`, optional
726+
- Default: `False`
727+
728+
**Remarks:**
729+
730+
- These lines are rendered with `GL.LINE` and will thus always be 1 pixel wide.
731+
- Wireframe and solid extrusions are exclusive, you'll need to create two layers
732+
with the same data if you want a combined rendering effect.
733+
"""
734+
735+
elevation_scale = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
736+
"""Elevation multiplier.
737+
738+
The final elevation is calculated by `elevationScale * getElevation(d)`.
739+
`elevationScale` is a handy property to scale all elevation without updating the
740+
data.
741+
742+
- Type: `float`, optional
743+
- Default: `1`
744+
"""
745+
746+
line_width_units = traitlets.Unicode(None, allow_none=True).tag(sync=True)
747+
"""
748+
The units of the line width, one of `'meters'`, `'common'`, and `'pixels'`. See
749+
[unit
750+
system](https://deck.gl/docs/developer-guide/coordinate-systems#supported-units).
751+
752+
- Type: `str`, optional
753+
- Default: `'meters'`
754+
"""
755+
756+
line_width_scale = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
757+
"""
758+
The line width multiplier that multiplied to all outlines of `Polygon` and
759+
`MultiPolygon` features if the `stroked` attribute is true.
760+
761+
- Type: `float`, optional
762+
- Default: `1`
763+
"""
764+
765+
line_width_min_pixels = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
766+
"""
767+
The minimum line width in pixels. This can be used to prevent the line from getting
768+
too small when zoomed out.
769+
770+
- Type: `float`, optional
771+
- Default: `0`
772+
"""
773+
774+
line_width_max_pixels = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
775+
"""
776+
The maximum line width in pixels. This can be used to prevent the line from getting
777+
too big when zoomed in.
778+
779+
- Type: `float`, optional
780+
- Default: `None`
781+
"""
782+
783+
line_joint_rounded = traitlets.Bool(None, allow_none=True).tag(sync=True)
784+
"""Type of joint. If `true`, draw round joints. Otherwise draw miter joints.
785+
786+
- Type: `bool`, optional
787+
- Default: `False`
788+
"""
789+
790+
line_miter_limit = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
791+
"""The maximum extent of a joint in ratio to the stroke width.
792+
793+
Only works if `line_joint_rounded` is false.
794+
795+
- Type: `float`, optional
796+
- Default: `4`
797+
"""
798+
799+
get_fill_color = ColorAccessor(None, allow_none=True)
800+
"""
801+
The fill color of each polygon in the format of `[r, g, b, [a]]`. Each channel is a
802+
number between 0-255 and `a` is 255 if not supplied.
803+
804+
- Type: [ColorAccessor][lonboard.traits.ColorAccessor], optional
805+
- If a single `list` or `tuple` is provided, it is used as the fill color for
806+
all polygons.
807+
- If a numpy or pyarrow array is provided, each value in the array will be used
808+
as the fill color for the polygon at the same row index.
809+
- Default: `[0, 0, 0, 255]`.
810+
"""
811+
812+
get_line_color = ColorAccessor(None, allow_none=True)
813+
"""
814+
The line color of each polygon in the format of `[r, g, b, [a]]`. Each channel is a
815+
number between 0-255 and `a` is 255 if not supplied.
816+
817+
Only applies if `extruded=True`.
818+
819+
- Type: [ColorAccessor][lonboard.traits.ColorAccessor], optional
820+
- If a single `list` or `tuple` is provided, it is used as the line color for
821+
all polygons.
822+
- If a numpy or pyarrow array is provided, each value in the array will be used
823+
as the line color for the polygon at the same row index.
824+
- Default: `[0, 0, 0, 255]`.
825+
"""
826+
827+
get_line_width = FloatAccessor(None, allow_none=True)
828+
"""
829+
The width of the outline of each polygon, in units specified by `line_width_units`
830+
(default `'meters'`).
831+
832+
- Type: [FloatAccessor][lonboard.traits.FloatAccessor], optional
833+
- If a number is provided, it is used as the outline width for all polygons.
834+
- If an array is provided, each value in the array will be used as the outline
835+
width for the polygon at the same row index.
836+
- Default: `1`.
837+
"""
838+
839+
get_elevation = FloatAccessor(None, allow_none=True)
840+
"""
841+
The elevation to extrude each polygon with, in meters.
842+
843+
Only applies if `extruded=True`.
844+
845+
- Type: [FloatAccessor][lonboard.traits.FloatAccessor], optional
846+
- If a number is provided, it is used as the width for all polygons.
847+
- If an array is provided, each value in the array will be used as the width for
848+
the polygon at the same row index.
849+
- Default: `1000`.
850+
"""
851+
852+
609853
class ScatterplotLayer(BaseArrowLayer):
610854
"""The `ScatterplotLayer` renders circles at given coordinates.
611855
@@ -1115,6 +1359,13 @@ class SolidPolygonLayer(BaseArrowLayer):
11151359
"""
11161360
The `SolidPolygonLayer` renders filled and/or extruded polygons.
11171361
1362+
!!! note
1363+
1364+
This layer is similar to the [`PolygonLayer`][lonboard.PolygonLayer] but will
1365+
not render an outline around polygons. In most cases, you'll want to use the
1366+
`PolygonLayer` directly, but for very large datasets not drawing the outline can
1367+
significantly improve performance, in which case you may want to use this layer.
1368+
11181369
**Example:**
11191370
11201371
From GeoPandas:
@@ -1210,6 +1461,12 @@ def from_geopandas(
12101461
12111462
- Type: `bool`, optional
12121463
- Default: `False`
1464+
1465+
**Remarks:**
1466+
1467+
- These lines are rendered with `GL.LINE` and will thus always be 1 pixel wide.
1468+
- Wireframe and solid extrusions are exclusive, you'll need to create two layers
1469+
with the same data if you want a combined rendering effect.
12131470
"""
12141471

12151472
elevation_scale = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
@@ -1220,12 +1477,6 @@ def from_geopandas(
12201477
12211478
- Type: `float`, optional
12221479
- Default: `1`
1223-
1224-
**Remarks:**
1225-
1226-
- These lines are rendered with `GL.LINE` and will thus always be 1 pixel wide.
1227-
- Wireframe and solid extrusions are exclusive, you'll need to create two layers
1228-
with the same data if you want a combined rendering effect.
12291480
"""
12301481

12311482
get_elevation = FloatAccessor(None, allow_none=True)

0 commit comments

Comments
 (0)