Skip to content

Commit

Permalink
PolygonLayer (#330)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
kylebarron authored Mar 28, 2024
1 parent 2bed40c commit e655269
Show file tree
Hide file tree
Showing 11 changed files with 467 additions and 56 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ gdf = gpd.GeoDataFrame(...)
viz(gdf)
```

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.
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.

## Documentation

Expand Down
5 changes: 5 additions & 0 deletions docs/api/layers/polygon-layer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# PolygonLayer

::: lonboard.PolygonLayer
options:
inherited_members: true
1 change: 1 addition & 0 deletions lonboard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
HeatmapLayer,
PathLayer,
PointCloudLayer,
PolygonLayer,
ScatterplotLayer,
SolidPolygonLayer,
)
Expand Down
263 changes: 257 additions & 6 deletions lonboard/_layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
HeatmapLayerKwargs,
PathLayerKwargs,
PointCloudLayerKwargs,
PolygonLayerKwargs,
ScatterplotLayerKwargs,
SolidPolygonLayerKwargs,
)
Expand Down Expand Up @@ -606,6 +607,249 @@ def __init__(self, **kwargs: BitmapTileLayerKwargs):
"""


class PolygonLayer(BaseArrowLayer):
"""The `PolygonLayer` renders filled, stroked and/or extruded polygons.
!!! note
This layer is essentially a combination of a [`PathLayer`][lonboard.PathLayer]
and a [`SolidPolygonLayer`][lonboard.SolidPolygonLayer]. This has some overhead
beyond a `SolidPolygonLayer`, so if you're looking for the maximum performance
with large data, you may want to use a `SolidPolygonLayer` directly.
**Example:**
From GeoPandas:
```py
import geopandas as gpd
from lonboard import Map, PolygonLayer
# A GeoDataFrame with Polygon or MultiPolygon geometries
gdf = gpd.GeoDataFrame()
layer = PolygonLayer.from_geopandas(
gdf,
get_fill_color=[255, 0, 0],
get_line_color=[0, 100, 100, 150],
)
m = Map(layer)
```
From [geoarrow-rust](https://geoarrow.github.io/geoarrow-rs/python/latest):
```py
from geoarrow.rust.core import read_parquet
from lonboard import Map, PolygonLayer
# Example: A GeoParquet file with Polygon or MultiPolygon geometries
table = read_parquet("path/to/file.parquet")
layer = PolygonLayer(
table=table,
get_fill_color=[255, 0, 0],
get_line_color=[0, 100, 100, 150],
)
m = Map(layer)
```
"""

def __init__(
self,
*,
table: pa.Table,
_rows_per_chunk: Optional[int] = None,
**kwargs: Unpack[PolygonLayerKwargs],
):
super().__init__(table=table, _rows_per_chunk=_rows_per_chunk, **kwargs)

@classmethod
def from_geopandas(
cls,
gdf: gpd.GeoDataFrame,
*,
auto_downcast: bool = True,
**kwargs: Unpack[PolygonLayerKwargs],
) -> Self:
return super().from_geopandas(gdf=gdf, auto_downcast=auto_downcast, **kwargs)

_layer_type = traitlets.Unicode("polygon").tag(sync=True)

table = PyarrowTableTrait(
allowed_geometry_types={EXTENSION_NAME.POLYGON, EXTENSION_NAME.MULTIPOLYGON}
)
"""A GeoArrow table with a Polygon or MultiPolygon column.
This is the fastest way to plot data from an existing GeoArrow source, such as
[geoarrow-rust](https://geoarrow.github.io/geoarrow-rs/python/latest) or
[geoarrow-pyarrow](https://geoarrow.github.io/geoarrow-python/main/index.html).
If you have a GeoPandas `GeoDataFrame`, use
[`from_geopandas`][lonboard.PolygonLayer.from_geopandas] instead.
"""

stroked = traitlets.Bool(None, allow_none=True).tag(sync=True)
"""Whether to draw an outline around the polygon (solid fill).
Note that both the outer polygon as well the outlines of any holes will be drawn.
- Type: `bool`, optional
- Default: `True`
"""

filled = traitlets.Bool(None, allow_none=True).tag(sync=True)
"""Whether to draw a filled polygon (solid fill).
Note that only the area between the outer polygon and any holes will be filled.
- Type: `bool`, optional
- Default: `True`
"""

extruded = traitlets.Bool(None, allow_none=True).tag(sync=True)
"""Whether to extrude the polygons.
Based on the elevations provided by the `getElevation` accessor.
If set to `false`, all polygons will be flat, this generates less geometry and is
faster than simply returning 0 from getElevation.
- Type: `bool`, optional
- Default: `False`
"""

wireframe = traitlets.Bool(None, allow_none=True).tag(sync=True)
"""
Whether to generate a line wireframe of the polygon. The outline will have
"horizontal" lines closing the top and bottom polygons and a vertical line
(a "strut") for each vertex on the polygon.
- Type: `bool`, optional
- Default: `False`
**Remarks:**
- These lines are rendered with `GL.LINE` and will thus always be 1 pixel wide.
- Wireframe and solid extrusions are exclusive, you'll need to create two layers
with the same data if you want a combined rendering effect.
"""

elevation_scale = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
"""Elevation multiplier.
The final elevation is calculated by `elevationScale * getElevation(d)`.
`elevationScale` is a handy property to scale all elevation without updating the
data.
- Type: `float`, optional
- Default: `1`
"""

line_width_units = traitlets.Unicode(None, allow_none=True).tag(sync=True)
"""
The units of the line width, one of `'meters'`, `'common'`, and `'pixels'`. See
[unit
system](https://deck.gl/docs/developer-guide/coordinate-systems#supported-units).
- Type: `str`, optional
- Default: `'meters'`
"""

line_width_scale = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
"""
The line width multiplier that multiplied to all outlines of `Polygon` and
`MultiPolygon` features if the `stroked` attribute is true.
- Type: `float`, optional
- Default: `1`
"""

line_width_min_pixels = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
"""
The minimum line width in pixels. This can be used to prevent the line from getting
too small when zoomed out.
- Type: `float`, optional
- Default: `0`
"""

line_width_max_pixels = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
"""
The maximum line width in pixels. This can be used to prevent the line from getting
too big when zoomed in.
- Type: `float`, optional
- Default: `None`
"""

line_joint_rounded = traitlets.Bool(None, allow_none=True).tag(sync=True)
"""Type of joint. If `true`, draw round joints. Otherwise draw miter joints.
- Type: `bool`, optional
- Default: `False`
"""

line_miter_limit = traitlets.Float(None, allow_none=True, min=0).tag(sync=True)
"""The maximum extent of a joint in ratio to the stroke width.
Only works if `line_joint_rounded` is false.
- Type: `float`, optional
- Default: `4`
"""

get_fill_color = ColorAccessor(None, allow_none=True)
"""
The fill color of each polygon in the format of `[r, g, b, [a]]`. Each channel is a
number between 0-255 and `a` is 255 if not supplied.
- Type: [ColorAccessor][lonboard.traits.ColorAccessor], optional
- If a single `list` or `tuple` is provided, it is used as the fill color for
all polygons.
- If a numpy or pyarrow array is provided, each value in the array will be used
as the fill color for the polygon at the same row index.
- Default: `[0, 0, 0, 255]`.
"""

get_line_color = ColorAccessor(None, allow_none=True)
"""
The line color of each polygon in the format of `[r, g, b, [a]]`. Each channel is a
number between 0-255 and `a` is 255 if not supplied.
Only applies if `extruded=True`.
- Type: [ColorAccessor][lonboard.traits.ColorAccessor], optional
- If a single `list` or `tuple` is provided, it is used as the line color for
all polygons.
- If a numpy or pyarrow array is provided, each value in the array will be used
as the line color for the polygon at the same row index.
- Default: `[0, 0, 0, 255]`.
"""

get_line_width = FloatAccessor(None, allow_none=True)
"""
The width of the outline of each polygon, in units specified by `line_width_units`
(default `'meters'`).
- Type: [FloatAccessor][lonboard.traits.FloatAccessor], optional
- If a number is provided, it is used as the outline width for all polygons.
- If an array is provided, each value in the array will be used as the outline
width for the polygon at the same row index.
- Default: `1`.
"""

get_elevation = FloatAccessor(None, allow_none=True)
"""
The elevation to extrude each polygon with, in meters.
Only applies if `extruded=True`.
- Type: [FloatAccessor][lonboard.traits.FloatAccessor], optional
- If a number is provided, it is used as the width for all polygons.
- If an array is provided, each value in the array will be used as the width for
the polygon at the same row index.
- Default: `1000`.
"""


class ScatterplotLayer(BaseArrowLayer):
"""The `ScatterplotLayer` renders circles at given coordinates.
Expand Down Expand Up @@ -1115,6 +1359,13 @@ class SolidPolygonLayer(BaseArrowLayer):
"""
The `SolidPolygonLayer` renders filled and/or extruded polygons.
!!! note
This layer is similar to the [`PolygonLayer`][lonboard.PolygonLayer] but will
not render an outline around polygons. In most cases, you'll want to use the
`PolygonLayer` directly, but for very large datasets not drawing the outline can
significantly improve performance, in which case you may want to use this layer.
**Example:**
From GeoPandas:
Expand Down Expand Up @@ -1210,6 +1461,12 @@ def from_geopandas(
- Type: `bool`, optional
- Default: `False`
**Remarks:**
- These lines are rendered with `GL.LINE` and will thus always be 1 pixel wide.
- Wireframe and solid extrusions are exclusive, you'll need to create two layers
with the same data if you want a combined rendering effect.
"""

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

get_elevation = FloatAccessor(None, allow_none=True)
Expand Down
Loading

0 comments on commit e655269

Please sign in to comment.