Skip to content

Commit a3ca99f

Browse files
Merge pull request #97 from eoda-dev/feature/color-utils
color and PMTiles utils
2 parents e0ae296 + 93d1ef1 commit a3ca99f

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+5053
-292
lines changed
Lines changed: 43 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# TODO: DEPRECATED: use maplibre.colors instead
12
from __future__ import annotations
23

34
from itertools import chain
@@ -9,26 +10,25 @@
910
print(e)
1011
branca_color_brewer = None
1112

12-
# TODO> Move to options
13+
CMAPS_JSON = "https://raw.githubusercontent.com/python-visualization/branca/main/branca/_schemes.json"
14+
15+
# TODO: Move to options
1316
FALLBACK_COLOR = "#000000"
1417

1518

1619
def color_brewer(cmap: str, n: int) -> list:
1720
n = int(n)
1821
if n == 2:
1922
colors = branca_color_brewer(cmap)
20-
return [colors[i] for i in [1, -1]]
23+
return [colors[i] for i in [0, -1]]
2124

2225
return branca_color_brewer(cmap, n)
2326

2427

2528
def create_categorical_color_expression(
2629
values: Any, column_name: str, cmap: str = "viridis"
27-
) -> list | None:
28-
if not color_brewer:
29-
return
30-
31-
unique_values = list(set(values))
30+
) -> tuple:
31+
unique_values = sorted(list(set(values)))
3232
colors = color_brewer(cmap, len(unique_values))
3333
expression = (
3434
["match", ["get", column_name]]
@@ -39,40 +39,54 @@ def create_categorical_color_expression(
3939
)
4040
+ [FALLBACK_COLOR]
4141
)
42-
return expression
42+
return expression, unique_values, colors
4343

4444

4545
# TODO: Allow to pass colors
46-
def create_numeric_color_expression_from_steps(
47-
column_name: str, steps: list, cmap="viridis"
48-
) -> list | None:
49-
colors = color_brewer(cmap, len(steps))
50-
# TODO: Extract this step to helper function
46+
def create_numeric_color_expression_from_breaks(
47+
column_name: str, breaks: list, cmap="viridis"
48+
) -> tuple:
49+
n = len(breaks)
50+
colors = color_brewer(cmap, n + 1)
5151
expression = (
5252
["step", ["get", column_name]]
5353
+ list(
54-
chain.from_iterable([[color, step] for color, step in zip(colors, steps)])
54+
chain.from_iterable(
55+
[[color, step] for color, step in zip(colors[0:n], breaks)]
56+
)
5557
)
56-
+ [FALLBACK_COLOR]
58+
+ [colors[-1]]
5759
)
58-
return expression
60+
return expression, breaks, colors
5961

6062

6163
def create_numeric_color_expression(
6264
values: Any, n: int, column_name: str, cmap: str = "viridis"
63-
) -> tuple | None:
65+
) -> tuple:
6466
step = (max(values) - min(values)) / n
65-
breaks = [min(values) + i * step for i in range(n)]
66-
colors = color_brewer(cmap, n + 1)
67+
# breaks = [min(values) + i * step for i in range(n)]
68+
breaks = [min(values) + step + i * step for i in range(n - 1)]
69+
return create_numeric_color_expression_from_breaks(column_name, breaks, cmap)
6770

68-
expression = (
69-
["step", ["get", column_name]]
70-
+ list(
71-
chain.from_iterable(
72-
[[color, step] for color, step in zip(colors[0:n], breaks)]
73-
)
74-
)
75-
+ [colors[-1]]
76-
)
7771

78-
return expression, breaks, colors
72+
def create_numeric_color_expression_from_quantiles(
73+
values: Any, q: list, column_name: str, cmap: str = "viridis"
74+
) -> tuple | None:
75+
try:
76+
import numpy as np
77+
except ImportError as e:
78+
print(e)
79+
return
80+
81+
breaks = np.quantile(values, q)
82+
return create_numeric_color_expression_from_breaks(column_name, breaks, cmap)
83+
84+
85+
def list_cmaps() -> list | None:
86+
try:
87+
import requests
88+
except ImportError as e:
89+
print(e)
90+
return
91+
92+
return list(requests.get(CMAPS_JSON).json().keys())

_deprecated/express.py

Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
# DEPRECATED:
2+
# See maplibre.__future__.express for new version
3+
4+
from __future__ import annotations
5+
6+
try:
7+
import pandas as pd
8+
from geopandas import GeoDataFrame, read_file
9+
except ImportError as e:
10+
print(e)
11+
pd = None
12+
GeoDataFrame = None
13+
read_file = None
14+
15+
from maplibre.controls import *
16+
from maplibre.layer import Layer, LayerType
17+
from maplibre.map import Map, MapOptions
18+
from maplibre.sources import GeoJSONSource
19+
from maplibre.utils import geopandas_to_geojson
20+
21+
from _deprecated.color_utils import *
22+
23+
CRS = "EPSG:4326"
24+
DEFAULT_COLOR = "darkred"
25+
26+
if pd is not None:
27+
28+
@pd.api.extensions.register_dataframe_accessor("maplibre")
29+
class MapLibreAccessor(object):
30+
def __init__(self, gdf: GeoDataFrame):
31+
self._gdf = gdf
32+
33+
def to_source(self) -> GeoJSONSource:
34+
return GeoJSONSource(data=geopandas_to_geojson(self._gdf))
35+
36+
37+
class GeoJSON(object):
38+
def __init__(
39+
self,
40+
data: GeoDataFrame | str,
41+
layer_type: LayerType | str,
42+
color_column: str = None,
43+
cmap: str = "viridis",
44+
n: int = None,
45+
q: list = None,
46+
breaks: list = None,
47+
**kwargs,
48+
):
49+
if isinstance(data, str):
50+
data = read_file(data)
51+
52+
if str(data.crs) != CRS:
53+
data = data.to_crs(CRS)
54+
55+
self.bounds = data.total_bounds
56+
57+
# Create layer
58+
layer_type = LayerType(layer_type).value
59+
kwargs["type"] = layer_type
60+
if "paint" not in kwargs:
61+
kwargs["paint"] = {f"{layer_type}-color": DEFAULT_COLOR}
62+
63+
self._layer = Layer(**kwargs)
64+
65+
# Set color expression
66+
# TODO: Extract this step to separate function
67+
if color_column:
68+
_breaks = None
69+
_categories = None
70+
if n is not None:
71+
color_expression, _breaks, _colors = create_numeric_color_expression(
72+
values=data[color_column], n=n, column_name=color_column, cmap=cmap
73+
)
74+
elif breaks is not None:
75+
color_expression, _breaks, _colors = (
76+
create_numeric_color_expression_from_breaks(
77+
column_name=color_column, breaks=breaks, cmap=cmap
78+
)
79+
)
80+
elif q is not None:
81+
color_expression, _breaks, _colors = (
82+
create_numeric_color_expression_from_quantiles(
83+
values=data[color_column],
84+
q=q,
85+
column_name=color_column,
86+
cmap=cmap,
87+
)
88+
)
89+
else:
90+
color_expression, _categories, _colors = (
91+
create_categorical_color_expression(
92+
values=data[color_column], column_name=color_column, cmap=cmap
93+
)
94+
)
95+
96+
self._layer.paint[f"{layer_type}-color"] = color_expression
97+
98+
self._layer.source = GeoJSONSource(data=geopandas_to_geojson(data))
99+
100+
@property
101+
def layer(self):
102+
return self._layer
103+
104+
def to_map(
105+
self,
106+
fit_bounds: bool = True,
107+
tooltip: bool = True,
108+
controls: list = [NavigationControl()],
109+
before_id: str = None,
110+
**kwargs,
111+
):
112+
map_options = MapOptions(**kwargs)
113+
if fit_bounds:
114+
map_options.bounds = self.bounds
115+
116+
m = Map(map_options)
117+
for control in controls:
118+
m.add_control(control)
119+
120+
m.add_layer(self._layer, before_id)
121+
if tooltip:
122+
m.add_tooltip(self._layer.id)
123+
124+
return m
125+
126+
127+
class Circle(GeoJSON):
128+
def __init__(
129+
self,
130+
data: GeoDataFrame | str,
131+
color_column: str = None,
132+
cmap: str = "viridis",
133+
n: int = None,
134+
q: list = None,
135+
breaks: list = None,
136+
**kwargs,
137+
):
138+
super().__init__(
139+
data, LayerType.CIRCLE, color_column, cmap, n, q, breaks, **kwargs
140+
)
141+
142+
143+
class Fill(GeoJSON):
144+
def __init__(
145+
self,
146+
data: GeoDataFrame | str,
147+
color_column: str = None,
148+
cmap: str = "viridis",
149+
n: int = None,
150+
q: list = None,
151+
breaks: list = None,
152+
fill_outline_color: str = None,
153+
**kwargs,
154+
):
155+
if "paint" not in kwargs and fill_outline_color is not None:
156+
kwargs["paint"] = {"fill-outline-color": fill_outline_color}
157+
158+
super().__init__(
159+
data, LayerType.FILL, color_column, cmap, n, q, breaks, **kwargs
160+
)
161+
162+
163+
class Line(GeoJSON):
164+
def __init__(
165+
self,
166+
data: GeoDataFrame | str,
167+
color_column: str = None,
168+
cmap: str = "viridis",
169+
n: int = None,
170+
q: list = None,
171+
breaks: list = None,
172+
**kwargs,
173+
):
174+
super().__init__(
175+
data, LayerType.LINE, color_column, cmap, n, q, breaks, **kwargs
176+
)
177+
178+
179+
class FillExtrusion(GeoJSON):
180+
def __init__(
181+
self,
182+
data: GeoDataFrame | str,
183+
color_column: str = None,
184+
cmap: str = "viridis",
185+
n: int = None,
186+
q: list = None,
187+
breaks: list = None,
188+
fill_extrusion_height: Any = None,
189+
# fill_extrusion_base: Any = None
190+
**kwargs,
191+
):
192+
super().__init__(
193+
data, LayerType.FILL_EXTRUSION, color_column, cmap, n, q, breaks, **kwargs
194+
)
195+
if fill_extrusion_height:
196+
if isinstance(fill_extrusion_height, str):
197+
fill_extrusion_height = ["get", fill_extrusion_height]
198+
199+
self._layer.paint["fill-extrusion-height"] = fill_extrusion_height

0 commit comments

Comments
 (0)