Skip to content

Commit 1217c03

Browse files
authored
feat: Add geohash and s2 layers (#1007)
Closes #885
1 parent 6777f4d commit 1217c03

File tree

10 files changed

+344
-13
lines changed

10 files changed

+344
-13
lines changed

lonboard/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@
1212
BitmapLayer,
1313
BitmapTileLayer,
1414
ColumnLayer,
15+
GeohashLayer,
16+
H3HexagonLayer,
1517
HeatmapLayer,
1618
PathLayer,
1719
PointCloudLayer,
1820
PolygonLayer,
21+
S2Layer,
1922
ScatterplotLayer,
2023
SolidPolygonLayer,
2124
TripsLayer,
@@ -29,11 +32,14 @@
2932
"BitmapLayer",
3033
"BitmapTileLayer",
3134
"ColumnLayer",
35+
"GeohashLayer",
36+
"H3HexagonLayer",
3237
"HeatmapLayer",
3338
"Map",
3439
"PathLayer",
3540
"PointCloudLayer",
3641
"PolygonLayer",
42+
"S2Layer",
3743
"ScatterplotLayer",
3844
"SolidPolygonLayer",
3945
"TripsLayer",

lonboard/layer/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@
1212
from ._base import BaseArrowLayer, BaseLayer
1313
from ._bitmap import BitmapLayer, BitmapTileLayer
1414
from ._column import ColumnLayer
15+
from ._geohash import GeohashLayer
1516
from ._h3 import H3HexagonLayer
1617
from ._heatmap import HeatmapLayer
1718
from ._path import PathLayer
1819
from ._point_cloud import PointCloudLayer
1920
from ._polygon import PolygonLayer, SolidPolygonLayer
21+
from ._s2 import S2Layer
2022
from ._scatterplot import ScatterplotLayer
2123
from ._trips import TripsLayer
2224

@@ -28,11 +30,13 @@
2830
"BitmapLayer",
2931
"BitmapTileLayer",
3032
"ColumnLayer",
33+
"GeohashLayer",
3134
"H3HexagonLayer",
3235
"HeatmapLayer",
3336
"PathLayer",
3437
"PointCloudLayer",
3538
"PolygonLayer",
39+
"S2Layer",
3640
"ScatterplotLayer",
3741
"SolidPolygonLayer",
3842
"TripsLayer",

lonboard/layer/_a5.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@
3030

3131

3232
class A5Layer(PolygonLayer):
33-
"""The `A5Layer` renders filled and/or stroked polygons based on the [A5](https://a5geo.org) geospatial indexing system."""
33+
"""The `A5Layer` renders filled and/or stroked polygons based on the [A5](https://a5geo.org) geospatial indexing system.
34+
35+
!!! warning
36+
This layer does not currently support auto-centering the map view based on the
37+
extent of the input.
38+
"""
3439

3540
def __init__(
3641
self,

lonboard/layer/_geohash.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
import traitlets as t
6+
7+
from lonboard._utils import auto_downcast as _auto_downcast
8+
9+
# Important to import from ._polygon to avoid circular imports
10+
from lonboard.layer._polygon import PolygonLayer
11+
from lonboard.traits import ArrowTableTrait, TextAccessor
12+
13+
if TYPE_CHECKING:
14+
import sys
15+
16+
import pandas as pd
17+
from arro3.core.types import ArrowStreamExportable
18+
19+
from lonboard.types.layer import GeohashLayerKwargs, TextAccessorInput
20+
21+
if sys.version_info >= (3, 11):
22+
from typing import Self
23+
else:
24+
from typing_extensions import Self
25+
26+
if sys.version_info >= (3, 12):
27+
from typing import Unpack
28+
else:
29+
from typing_extensions import Unpack
30+
31+
32+
class GeohashLayer(PolygonLayer):
33+
"""The `GeohashLayer` renders filled and/or stroked polygons based on the [Geohash](https://en.wikipedia.org/wiki/Geohash) geospatial indexing system.
34+
35+
!!! warning
36+
This layer does not currently support auto-centering the map view based on the
37+
extent of the input.
38+
"""
39+
40+
def __init__(
41+
self,
42+
table: ArrowStreamExportable,
43+
*,
44+
get_geohash: TextAccessorInput,
45+
_rows_per_chunk: int | None = None,
46+
**kwargs: Unpack[GeohashLayerKwargs],
47+
) -> None:
48+
"""Create a new GeohashLayer.
49+
50+
Args:
51+
table: An Arrow table with properties to associate with the geohashes.
52+
53+
Keyword Args:
54+
get_geohash: The identifier of each geohash.
55+
kwargs: Extra args passed down as GeohashLayer attributes.
56+
57+
"""
58+
super().__init__(
59+
table=table,
60+
get_geohash=get_geohash,
61+
_rows_per_chunk=_rows_per_chunk,
62+
**kwargs,
63+
)
64+
65+
@classmethod
66+
def from_pandas(
67+
cls,
68+
df: pd.DataFrame,
69+
*,
70+
get_geohash: TextAccessorInput,
71+
auto_downcast: bool = True,
72+
**kwargs: Unpack[GeohashLayerKwargs],
73+
) -> Self:
74+
"""Create a new GeohashLayer from a pandas DataFrame.
75+
76+
Args:
77+
df: a Pandas DataFrame with properties to associate with geohashes.
78+
79+
Keyword Args:
80+
get_geohash: geohash identifiers.
81+
auto_downcast: Whether to save memory on input by casting to smaller types. Defaults to True.
82+
kwargs: Extra args passed down as GeohashLayer attributes.
83+
84+
"""
85+
try:
86+
import pyarrow as pa
87+
except ImportError as e:
88+
raise ImportError(
89+
"pyarrow required for converting GeoPandas to arrow.\n"
90+
"Run `pip install pyarrow`.",
91+
) from e
92+
93+
if auto_downcast:
94+
# Note: we don't deep copy because we don't need to clone geometries
95+
df = _auto_downcast(df.copy()) # type: ignore
96+
97+
table = pa.Table.from_pandas(df)
98+
return cls(table, get_geohash=get_geohash, **kwargs)
99+
100+
_layer_type = t.Unicode("geohash").tag(sync=True)
101+
102+
table = ArrowTableTrait(geometry_required=False)
103+
"""An Arrow table with properties to associate with the geohashes.
104+
105+
If you have a Pandas `DataFrame`, use
106+
[`from_pandas`][lonboard.GeohashLayer.from_pandas] instead.
107+
"""
108+
109+
get_geohash = TextAccessor()
110+
"""The cell identifier of each geohash.
111+
112+
Accepts either an array of strings or uint64 integers representing geohash IDs.
113+
114+
- Type: [TextAccessor][lonboard.traits.TextAccessor]
115+
"""

lonboard/layer/_s2.py

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
from __future__ import annotations
2+
3+
from typing import TYPE_CHECKING
4+
5+
import traitlets as t
6+
7+
from lonboard._utils import auto_downcast as _auto_downcast
8+
9+
# Important to import from ._polygon to avoid circular imports
10+
from lonboard.layer._polygon import PolygonLayer
11+
from lonboard.traits import ArrowTableTrait, TextAccessor
12+
13+
if TYPE_CHECKING:
14+
import sys
15+
16+
import pandas as pd
17+
from arro3.core.types import ArrowStreamExportable
18+
19+
from lonboard.types.layer import S2LayerKwargs, TextAccessorInput
20+
21+
if sys.version_info >= (3, 11):
22+
from typing import Self
23+
else:
24+
from typing_extensions import Self
25+
26+
if sys.version_info >= (3, 12):
27+
from typing import Unpack
28+
else:
29+
from typing_extensions import Unpack
30+
31+
32+
class S2Layer(PolygonLayer):
33+
"""The `S2Layer` renders filled and/or stroked polygons based on the [S2](http://s2geometry.io/) geospatial indexing system.
34+
35+
!!! warning
36+
This layer does not currently support auto-centering the map view based on the
37+
extent of the input.
38+
"""
39+
40+
def __init__(
41+
self,
42+
table: ArrowStreamExportable,
43+
*,
44+
get_s2_token: TextAccessorInput,
45+
_rows_per_chunk: int | None = None,
46+
**kwargs: Unpack[S2LayerKwargs],
47+
) -> None:
48+
"""Create a new S2Layer.
49+
50+
Args:
51+
table: An Arrow table with properties to associate with the S2 cells.
52+
53+
Keyword Args:
54+
get_s2_token: The identifier of each S2 cell.
55+
kwargs: Extra args passed down as S2Layer attributes.
56+
57+
"""
58+
super().__init__(
59+
table=table,
60+
get_s2_token=get_s2_token,
61+
_rows_per_chunk=_rows_per_chunk,
62+
**kwargs,
63+
)
64+
65+
@classmethod
66+
def from_pandas(
67+
cls,
68+
df: pd.DataFrame,
69+
*,
70+
get_s2_token: TextAccessorInput,
71+
auto_downcast: bool = True,
72+
**kwargs: Unpack[S2LayerKwargs],
73+
) -> Self:
74+
"""Create a new S2Layer from a pandas DataFrame.
75+
76+
Args:
77+
df: a Pandas DataFrame with properties to associate with S2 cells.
78+
79+
Keyword Args:
80+
get_s2_token: S2 cell identifier of each S2 hexagon.
81+
auto_downcast: Whether to save memory on input by casting to smaller types. Defaults to True.
82+
kwargs: Extra args passed down as S2Layer attributes.
83+
84+
"""
85+
try:
86+
import pyarrow as pa
87+
except ImportError as e:
88+
raise ImportError(
89+
"pyarrow required for converting GeoPandas to arrow.\n"
90+
"Run `pip install pyarrow`.",
91+
) from e
92+
93+
if auto_downcast:
94+
# Note: we don't deep copy because we don't need to clone geometries
95+
df = _auto_downcast(df.copy()) # type: ignore
96+
97+
table = pa.Table.from_pandas(df)
98+
return cls(table, get_s2_token=get_s2_token, **kwargs)
99+
100+
_layer_type = t.Unicode("s2").tag(sync=True)
101+
102+
table = ArrowTableTrait(geometry_required=False)
103+
"""An Arrow table with properties to associate with the S2 cells.
104+
105+
If you have a Pandas `DataFrame`, use
106+
[`from_pandas`][lonboard.S2Layer.from_pandas] instead.
107+
"""
108+
109+
get_s2_token = TextAccessor()
110+
"""The cell identifier of each S2 cell.
111+
112+
Accepts either an array of strings or uint64 integers representing S2 cell IDs.
113+
114+
- Type: [TextAccessor][lonboard.traits.TextAccessor]
115+
"""

lonboard/types/layer.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,14 @@ class A5LayerKwargs(PolygonLayerKwargs, total=False):
216216
pass
217217

218218

219+
class S2LayerKwargs(PolygonLayerKwargs, total=False):
220+
pass
221+
222+
223+
class GeohashLayerKwargs(PolygonLayerKwargs, total=False):
224+
pass
225+
226+
219227
class ScatterplotLayerKwargs(BaseLayerKwargs, total=False):
220228
radius_units: Units
221229
radius_scale: IntFloat

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
"@deck.gl/mapbox": "^9.2.2",
99
"@deck.gl/mesh-layers": "^9.2.2",
1010
"@deck.gl/react": "^9.2.2",
11-
"@geoarrow/deck.gl-layers": "^0.4.0-beta.5",
11+
"@geoarrow/deck.gl-layers": "^0.4.0-beta.6",
1212
"@geoarrow/geoarrow-js": "^0.3.2",
1313
"@nextui-org/react": "^2.4.8",
1414
"@xstate/react": "^6.0.0",

src/model/layer/index.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ import { PathModel } from "./path.js";
99
import { PointCloudModel } from "./point-cloud.js";
1010
import {
1111
A5Model,
12+
GeohashModel,
1213
H3HexagonModel,
1314
PolygonModel,
15+
S2Model,
1416
SolidPolygonModel,
1517
} from "./polygon.js";
1618
import { ScatterplotModel } from "./scatterplot.js";
@@ -62,6 +64,10 @@ export async function initializeLayer(
6264
layerModel = new ColumnModel(model, updateStateCallback);
6365
break;
6466

67+
case GeohashModel.layerType:
68+
layerModel = new GeohashModel(model, updateStateCallback);
69+
break;
70+
6571
case H3HexagonModel.layerType:
6672
layerModel = new H3HexagonModel(model, updateStateCallback);
6773
break;
@@ -82,6 +88,10 @@ export async function initializeLayer(
8288
layerModel = new PolygonModel(model, updateStateCallback);
8389
break;
8490

91+
case S2Model.layerType:
92+
layerModel = new S2Model(model, updateStateCallback);
93+
break;
94+
8595
case ScatterplotModel.layerType:
8696
layerModel = new ScatterplotModel(model, updateStateCallback);
8797
break;

0 commit comments

Comments
 (0)