Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

color and PMTiles utils #97

Merged
merged 115 commits into from
Dec 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
115 commits
Select commit Hold shift + click to select a range
77cabc0
Add color utils
Jul 12, 2024
7edf0b4
Add save method to map
Jul 12, 2024
21a3981
Add maplibre express CoreLayer
Jul 12, 2024
c9ccb41
Support numeric color columns
Jul 12, 2024
fcd3b98
Fix color expressions
Jul 12, 2024
1e0a299
Add vancouver blocks express example
Jul 12, 2024
88850cc
Create color expression from quantiles
Jul 13, 2024
eb688dc
Refactor color utils
Jul 13, 2024
71d6f9a
Add quantiles as q parameter
Jul 13, 2024
49051ce
Add method to list cmaps
Jul 13, 2024
bf25dfd
Add default color and refactor
Jul 14, 2024
df50922
Add outline color
Jul 14, 2024
15bec27
Refactor and add helper to get pmtile meta data
Jul 15, 2024
8b2363a
Move pmtiles utils to separate file
Jul 15, 2024
8391b97
PMTiles class
Jul 15, 2024
cdc34cc
Add PMTiles header class
Jul 16, 2024
d72bc12
Fix test
Jul 16, 2024
ad67f34
Create source based on tile type
Jul 16, 2024
81969a8
Return vector source
Jul 16, 2024
b4ec856
Fix missing h3 dep for geo layers
Jul 16, 2024
2784de3
Create basemap from pmtiles
Jul 17, 2024
6587bda
Refactor
Jul 17, 2024
45e8426
Merge branch 'dev' into feature/color-utils
Jul 18, 2024
57653b1
Refactor
Jul 18, 2024
5c671bd
Use fixed h3 version
Jul 18, 2024
3959490
Support h3 deck layers in shiny apps
Jul 18, 2024
49a243c
Extract maplibre version
Jul 18, 2024
d152b0f
Support deck.gl geo layers
Jul 18, 2024
ea6ff10
Merge pull request #102 from eodaGmbH/feature/deck-geolayers-ipywidget
crazycapivara Jul 18, 2024
619f17c
Catch error
Jul 19, 2024
851b870
Get pmtiles header from local file
Jul 19, 2024
b25d819
Support metadata from local pmtiles
Jul 19, 2024
103b500
Refactor
Jul 19, 2024
b1b5f97
Add simple features source
Jul 20, 2024
e4486b8
Allow geopandas sources
Jul 21, 2024
94eb3ba
Add optinal to layer attributes
Jul 21, 2024
40a8c83
Extract BaseModel to _core
Jul 21, 2024
d6ebbe0
Rename BaseModel to MapLibreBaseModel
Jul 21, 2024
4c51361
Allow arbitrary types
Jul 21, 2024
a1a080d
Allow geopandas source for layers
Jul 21, 2024
219151f
Add test for SimpleFeatures
Jul 21, 2024
f81f8d4
Remove dead code
Jul 21, 2024
9b4f6cf
Add layers and sources parameter to Map
Jul 21, 2024
fb97161
Add simple layers
Jul 22, 2024
4a0fa33
Add bounds property to layer
Jul 22, 2024
a74dfc7
Add position param to controls
Jul 24, 2024
3c2b7c3
Add controls param to map
Jul 24, 2024
3014796
Add interpolate expression
Jul 25, 2024
0fa4e7d
Add step expression
Jul 25, 2024
9200b04
Add example using pydeck.Layer
Jul 25, 2024
013fb16
Support pydeck layers
Jul 25, 2024
401c866
Add comment
Jul 25, 2024
a146587
Allow GeoDataFrame in set Data
Jul 25, 2024
90ef826
Update changelog
Jul 25, 2024
40ab30a
Add additional params to MapWidget
Jul 25, 2024
75349b5
color_match_expr
Jul 25, 2024
b2628db
Add quantiles expression
Jul 26, 2024
b316144
Add _future
Jul 26, 2024
da93408
Add py.typed
Jul 26, 2024
75c136f
Move color expressions to expressions
Jul 26, 2024
c182855
Refactor expressions
Jul 26, 2024
e70256d
Refactor expressions
Jul 26, 2024
eff3d3d
Add example datasets
Jul 27, 2024
448c997
Add filter example
Jul 27, 2024
cda034f
Add filter expressions
Jul 27, 2024
2a3b555
Update docs
Jul 27, 2024
2c5cce2
Refactor __future__ code
Jul 27, 2024
c96ba4b
Add dataset
Jul 27, 2024
dad061e
Add urban areas dataset
Jul 27, 2024
c081be2
Add fit_bounds method
Jul 27, 2024
a3c2935
Update changelog
Jul 27, 2024
5117006
Add new express implementation
Jul 28, 2024
dbccf38
Store path
Jul 28, 2024
0ba1196
Use url as source if possible
Jul 28, 2024
72623b4
Add earthquakes dataset
Jul 28, 2024
fc2b3e2
Add circle express layer
Jul 28, 2024
d14d6d6
Add color bin expression
Jul 28, 2024
8b444dd
Add map_this
Jul 29, 2024
6c6e1d1
Add Wes Anderson color palettes
Jul 31, 2024
4267202
Update settings
Jul 31, 2024
0a8f371
Set default fill_outline_color
Jul 31, 2024
6a8d8ea
Fix settings
Jul 31, 2024
d0b417f
Move color_utils to _deprecated
Jul 31, 2024
f826cbc
Move current express to _deprecated
Jul 31, 2024
451109d
Use fill_outline_color from settings
Jul 31, 2024
86f7ee6
Move express from __future__ to root
Jul 31, 2024
e03bd95
Typo
Jul 31, 2024
5b6150f
Add default layer styles
Jul 31, 2024
d52b917
Add test
Jul 31, 2024
1d18d72
Add maptiler support
Aug 29, 2024
2b1e0ec
Typo
Aug 29, 2024
1bce806
Update docs
Aug 29, 2024
4820dd2
Update changelog
Aug 29, 2024
0796929
Fix express
Aug 29, 2024
bc0e99d
Use model_post_init
Aug 29, 2024
1cc130e
Refactor simple layer
Aug 29, 2024
b446877
Update docs
Aug 29, 2024
f7a514d
Add height param to ipywidget
Aug 29, 2024
4ce3f11
Rename Maptiler to MapTiler
Sep 2, 2024
1f91856
Add basemap demotiles constant
Sep 2, 2024
08ea1c9
Add extrusion layer to express
Sep 2, 2024
d7b5226
Use specific maplibre version in html template
Sep 2, 2024
f460279
Refactor
Sep 2, 2024
ba4373b
Rename settings.py to config.py
Sep 2, 2024
4579225
Rename settings to options
Sep 2, 2024
a00fc27
Import options in init
Sep 2, 2024
ae010bb
Add TODO
Sep 2, 2024
00bab7b
Add TODO
Sep 2, 2024
f80327a
Remove dead code
Sep 2, 2024
9de16ba
Refactor fit_bounds
Sep 2, 2024
8b33025
Update docs
Sep 2, 2024
36381c1
Add data
Sep 6, 2024
fa78654
Add comments
Sep 6, 2024
cde470d
Merge branch 'dev' into feature/color-utils
Oct 31, 2024
93d1ef1
v0.2.7
Dec 14, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 43 additions & 29 deletions _experimental/color_utils.py → _deprecated/color_utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# TODO: DEPRECATED: use maplibre.colors instead
from __future__ import annotations

from itertools import chain
Expand All @@ -9,26 +10,25 @@
print(e)
branca_color_brewer = None

# TODO> Move to options
CMAPS_JSON = "https://raw.githubusercontent.com/python-visualization/branca/main/branca/_schemes.json"

# TODO: Move to options
FALLBACK_COLOR = "#000000"


def color_brewer(cmap: str, n: int) -> list:
n = int(n)
if n == 2:
colors = branca_color_brewer(cmap)
return [colors[i] for i in [1, -1]]
return [colors[i] for i in [0, -1]]

return branca_color_brewer(cmap, n)


def create_categorical_color_expression(
values: Any, column_name: str, cmap: str = "viridis"
) -> list | None:
if not color_brewer:
return

unique_values = list(set(values))
) -> tuple:
unique_values = sorted(list(set(values)))
colors = color_brewer(cmap, len(unique_values))
expression = (
["match", ["get", column_name]]
Expand All @@ -39,40 +39,54 @@ def create_categorical_color_expression(
)
+ [FALLBACK_COLOR]
)
return expression
return expression, unique_values, colors


# TODO: Allow to pass colors
def create_numeric_color_expression_from_steps(
column_name: str, steps: list, cmap="viridis"
) -> list | None:
colors = color_brewer(cmap, len(steps))
# TODO: Extract this step to helper function
def create_numeric_color_expression_from_breaks(
column_name: str, breaks: list, cmap="viridis"
) -> tuple:
n = len(breaks)
colors = color_brewer(cmap, n + 1)
expression = (
["step", ["get", column_name]]
+ list(
chain.from_iterable([[color, step] for color, step in zip(colors, steps)])
chain.from_iterable(
[[color, step] for color, step in zip(colors[0:n], breaks)]
)
)
+ [FALLBACK_COLOR]
+ [colors[-1]]
)
return expression
return expression, breaks, colors


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

expression = (
["step", ["get", column_name]]
+ list(
chain.from_iterable(
[[color, step] for color, step in zip(colors[0:n], breaks)]
)
)
+ [colors[-1]]
)

return expression, breaks, colors
def create_numeric_color_expression_from_quantiles(
values: Any, q: list, column_name: str, cmap: str = "viridis"
) -> tuple | None:
try:
import numpy as np
except ImportError as e:
print(e)
return

breaks = np.quantile(values, q)
return create_numeric_color_expression_from_breaks(column_name, breaks, cmap)


def list_cmaps() -> list | None:
try:
import requests
except ImportError as e:
print(e)
return

return list(requests.get(CMAPS_JSON).json().keys())
199 changes: 199 additions & 0 deletions _deprecated/express.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
# DEPRECATED:
# See maplibre.__future__.express for new version

from __future__ import annotations

try:
import pandas as pd
from geopandas import GeoDataFrame, read_file
except ImportError as e:
print(e)
pd = None
GeoDataFrame = None
read_file = None

from maplibre.controls import *
from maplibre.layer import Layer, LayerType
from maplibre.map import Map, MapOptions
from maplibre.sources import GeoJSONSource
from maplibre.utils import geopandas_to_geojson

from _deprecated.color_utils import *

CRS = "EPSG:4326"
DEFAULT_COLOR = "darkred"

if pd is not None:

@pd.api.extensions.register_dataframe_accessor("maplibre")
class MapLibreAccessor(object):
def __init__(self, gdf: GeoDataFrame):
self._gdf = gdf

def to_source(self) -> GeoJSONSource:
return GeoJSONSource(data=geopandas_to_geojson(self._gdf))


class GeoJSON(object):
def __init__(
self,
data: GeoDataFrame | str,
layer_type: LayerType | str,
color_column: str = None,
cmap: str = "viridis",
n: int = None,
q: list = None,
breaks: list = None,
**kwargs,
):
if isinstance(data, str):
data = read_file(data)

if str(data.crs) != CRS:
data = data.to_crs(CRS)

self.bounds = data.total_bounds

# Create layer
layer_type = LayerType(layer_type).value
kwargs["type"] = layer_type
if "paint" not in kwargs:
kwargs["paint"] = {f"{layer_type}-color": DEFAULT_COLOR}

self._layer = Layer(**kwargs)

# Set color expression
# TODO: Extract this step to separate function
if color_column:
_breaks = None
_categories = None
if n is not None:
color_expression, _breaks, _colors = create_numeric_color_expression(
values=data[color_column], n=n, column_name=color_column, cmap=cmap
)
elif breaks is not None:
color_expression, _breaks, _colors = (
create_numeric_color_expression_from_breaks(
column_name=color_column, breaks=breaks, cmap=cmap
)
)
elif q is not None:
color_expression, _breaks, _colors = (
create_numeric_color_expression_from_quantiles(
values=data[color_column],
q=q,
column_name=color_column,
cmap=cmap,
)
)
else:
color_expression, _categories, _colors = (
create_categorical_color_expression(
values=data[color_column], column_name=color_column, cmap=cmap
)
)

self._layer.paint[f"{layer_type}-color"] = color_expression

self._layer.source = GeoJSONSource(data=geopandas_to_geojson(data))

@property
def layer(self):
return self._layer

def to_map(
self,
fit_bounds: bool = True,
tooltip: bool = True,
controls: list = [NavigationControl()],
before_id: str = None,
**kwargs,
):
map_options = MapOptions(**kwargs)
if fit_bounds:
map_options.bounds = self.bounds

m = Map(map_options)
for control in controls:
m.add_control(control)

m.add_layer(self._layer, before_id)
if tooltip:
m.add_tooltip(self._layer.id)

return m


class Circle(GeoJSON):
def __init__(
self,
data: GeoDataFrame | str,
color_column: str = None,
cmap: str = "viridis",
n: int = None,
q: list = None,
breaks: list = None,
**kwargs,
):
super().__init__(
data, LayerType.CIRCLE, color_column, cmap, n, q, breaks, **kwargs
)


class Fill(GeoJSON):
def __init__(
self,
data: GeoDataFrame | str,
color_column: str = None,
cmap: str = "viridis",
n: int = None,
q: list = None,
breaks: list = None,
fill_outline_color: str = None,
**kwargs,
):
if "paint" not in kwargs and fill_outline_color is not None:
kwargs["paint"] = {"fill-outline-color": fill_outline_color}

super().__init__(
data, LayerType.FILL, color_column, cmap, n, q, breaks, **kwargs
)


class Line(GeoJSON):
def __init__(
self,
data: GeoDataFrame | str,
color_column: str = None,
cmap: str = "viridis",
n: int = None,
q: list = None,
breaks: list = None,
**kwargs,
):
super().__init__(
data, LayerType.LINE, color_column, cmap, n, q, breaks, **kwargs
)


class FillExtrusion(GeoJSON):
def __init__(
self,
data: GeoDataFrame | str,
color_column: str = None,
cmap: str = "viridis",
n: int = None,
q: list = None,
breaks: list = None,
fill_extrusion_height: Any = None,
# fill_extrusion_base: Any = None
**kwargs,
):
super().__init__(
data, LayerType.FILL_EXTRUSION, color_column, cmap, n, q, breaks, **kwargs
)
if fill_extrusion_height:
if isinstance(fill_extrusion_height, str):
fill_extrusion_height = ["get", fill_extrusion_height]

self._layer.paint["fill-extrusion-height"] = fill_extrusion_height
Loading
Loading