diff --git a/examples/explore.ipynb b/examples/explore.ipynb new file mode 100644 index 00000000..7b9c92a2 --- /dev/null +++ b/examples/explore.ipynb @@ -0,0 +1,3721 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 1, + "id": "a76948e2-eda2-4c6f-8fe2-341b67ac5531", + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "\n", + "from warnings import warn\n", + "\n", + "import geodatasets\n", + "import geopandas as gpd\n", + "import lonboard.geopandas\n", + "import numpy as np\n", + "from lonboard import PolygonLayer\n", + "from mapclassify import classify" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "1e09eb28-7b9a-4dd6-a11a-f9d26bd5e793", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Author: eli knaap\n", + "\n", + "mapclassify: 2.8.1\n", + "lonboard : 0.10.3\n", + "geopandas : 1.0.1\n", + "geodatasets: 2024.8.0\n", + "numpy : 2.0.2\n", + "\n" + ] + } + ], + "source": [ + "%load_ext watermark\n", + "%watermark -a 'eli knaap' -iv" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "3902a38a-84c2-492a-b8b8-fad2b4afef72", + "metadata": {}, + "outputs": [], + "source": [ + "gdf = gpd.read_file(geodatasets.get_path(\"geoda.milwaukee1\"))" + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "9b0db679-e16b-4111-b24f-19de0c544fc5", + "metadata": {}, + "outputs": [], + "source": [ + "gdf = gdf.to_crs(gdf.estimate_utm_crs())" + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "0e071041-a9a6-4d9a-9c3f-e8b252bda744", + "metadata": {}, + "outputs": [], + "source": [ + "gdf = gdf[[\"HH_INC\", \"geometry\"]]" + ] + }, + { + "cell_type": "markdown", + "id": "d3b67d78-8ddc-4691-9f09-3fbb81a65cd0", + "metadata": {}, + "source": [ + "## Simple Map" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "11c477e4-b3c0-489c-a85c-2a9ac9e48d8c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf.explore()" + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "22485cad-e47b-409a-accf-0da27419db7b", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b82e0b073c10424a8d206bab1ef30138", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=, cu…" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from lonboard import basemap\n", + "\n", + "gdf.lb.explore(\n", + " tiles=\"CartoDB Positron\",\n", + ")" + ] + }, + { + "cell_type": "markdown", + "id": "93da152a-6409-4764-ab24-f773e3d09fb9", + "metadata": {}, + "source": [ + "## boorish (unclassed :P) choropleth" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "487cafd3-4735-4c2c-b19f-6ff6dcdfe93c", + "metadata": {}, + "outputs": [ + { + "data": { + "text/html": [ + "
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf.explore(\"HH_INC\", tiles=\"CartoDB Darkmatter\")" + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "f044e655-32be-4ce1-b015-223ed9c71de1", + "metadata": { + "scrolled": true + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "715b167d7ba24bf2b4c58f7e5c395164", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf.explore(\n", + " \"HH_INC\",\n", + " scheme=\"quantiles\",\n", + " k=6,\n", + " cmap=\"YlOrBr\",\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "id": "e70b95c3-09b2-40be-8f5e-fd1482388be2", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "aa1ffb245bff469a9d2d7b89174050b9", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "classify_kwds = {\"bins\": [20000, 40000, 800000, 2000000]}\n", + "\n", + "gdf.explore(\n", + " \"HH_INC\",\n", + " cmap=\"YlOrBr\",\n", + " scheme=\"user_defined\",\n", + " classification_kwds=classify_kwds,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "id": "ad5c4e9f-7dcc-4144-b5ba-77987cea0def", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7dcbea826d014ef49cdf171f8129baf0", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = linedf.explore(\n", + " \"len\", cmap=\"viridis_r\", scheme=\"quantiles\", k=10, tiles=\"CartoDB Darkmatter\"\n", + ")\n", + "gdf.set_geometry(gdf.centroid).explore(color=\"magenta\", m=m)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "id": "54df1700-2971-4813-9842-aa0116b46e20", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n", + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "c1b361ce82bb46b7b820ad023e0192a7", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 21, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf.explore(\n", + " \"i\",\n", + " categorical=True,\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "id": "a7f86337-b4ed-4ac6-929c-13d053f21767", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "84e99ffd7cd042f99b6eb060f3ba960b", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=
Make this Notebook Trusted to load map: File -> Trust Notebook
" + ], + "text/plain": [ + "" + ] + }, + "execution_count": 26, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "gdf.explore(\"q5\", categorical=True, cmap=\"tab20b\", tiles=\"CartoDB Positron\")" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "id": "9bd34a9c-6a45-4f53-a9ca-19324e6aa32d", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "ec59bf072c244f018f73c1a412af0232", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style=, cu…" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "m = gdf.lb.explore(\n", + " \"q5\",\n", + " categorical=True,\n", + " cmap=\"tab20b\",\n", + " nan_color=[0, 0, 0, 0],\n", + " tiles=\"CartoDB Positron\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "4e926525-d0a8-40dd-a195-89a774bbe8a4", + "metadata": {}, + "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 28, + "id": "c54f7abe-ca10-467a-baf0-ff6c499ea4e8", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + } + ], + "source": [ + "m = gdf.lb.explore(\"q5\", categorical=True, cmap=\"RdBu\", alpha=0.5)" + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "id": "433064c8-6f54-43fe-9e73-1754100a6d50", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "be7189af7be64473a06345cd06fc62d0", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style= widget:\n", + " layer.get_fill_color = get_color_array(vals, classifier, k=k, cmap=cmap)" + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "id": "cb646dce-d4ef-48d4-8016-f8a9ed9fb97e", + "metadata": {}, + "outputs": [], + "source": [ + "from matplotlib import colormaps" + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "id": "0577a3a1-74e2-4d89-b1ce-e4ddd27340c4", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "089e2b4b9de84b84a3db28f87cc93126", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style= >" + ] + }, + "execution_count": 39, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "from ipywidgets import fixed, interact\n", + "\n", + "interact(\n", + " choro,\n", + " vals=fixed(gdf.HH_INC),\n", + " classifier=list(_classifiers.keys()),\n", + " k=range(3, 10),\n", + " cmap=list(colormaps.keys()),\n", + " layer=fixed(m.layers[0]),\n", + ")" + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "id": "73c7a823-c3fd-4ade-aa73-5709b0ee29d7", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/Users/knaaptime/Dropbox/projects/lonboard/lonboard/_geoarrow/ops/reproject.py:115: UserWarning: Input being reprojected to EPSG:4326 CRS.\n", + "Lonboard is only able to render data in EPSG:4326 projection.\n", + " warnings.warn(\n" + ] + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d71e678ca73746efafc716406fede27e", + "version_major": 2, + "version_minor": 1 + }, + "text/plain": [ + "Map(basemap_style= None: # noqa: ANN001, D107 + self._validate(pandas_obj) + self._obj = pandas_obj + + @staticmethod + def _validate(obj) -> None: # noqa: ANN001 + if not isinstance(obj, gpd.GeoDataFrame): + raise TypeError("must be a geodataframe") + + def explore( # noqa: PLR0913 + self, + column: str | None = None, + cmap: str | None = None, + scheme: str | None = None, + k: int | None = 6, + categorical: bool = False, # noqa: FBT001, FBT002 + elevation: str | np.ndarray = None, + elevation_scale: float | None = 1, + alpha: float | None = 1, + layer_kwargs: dict[str, Any] | None = None, + map_kwargs: dict[str, Any] | None = None, + classification_kwds: dict[str, Any] | None = None, + nan_color: list[int] | np.ndarray[int] | None = None, + color: str | None = None, + vmin: float | None = None, + vmax: float | None = None, + wireframe: bool = False, # noqa: FBT001, FBT002 + tiles: str | None = None, + highlight: bool = False, # noqa: FBT001, FBT002 + m: Map | None = None, + ) -> Map: + """Explore a dataframe using lonboard and deckgl. + + Keyword Args: + column : Name of column on dataframe to visualize on map. + cmap : Name of matplotlib colormap to use. + scheme : Name of a classification scheme defined by mapclassify.Classifier. + k : Number of classes to generate. Defaults to 6. + categorical : Whether the data should be treated as categorical or + continuous. + elevation : Name of column on the dataframe used to extrude each geometry or + an array-like in the same order as observations. Defaults to None. + elevation_scale : Constant scaler multiplied by elevation value. + alpha : Alpha (opacity) parameter in the range (0,1) passed to + mapclassify.util.get_color_array. + layer_kwargs : Additional keyword arguments passed to lonboard.viz layer + arguments (either polygon_kwargs, scatterplot_kwargs, or path_kwargs, + depending on input geometry type). + map_kwargs : Additional keyword arguments passed to lonboard.viz map_kwargs. + classification_kwds : Additional keyword arguments passed to + `mapclassify.classify`. + nan_color : Color used to shade NaN observations formatted as an RGBA list. + Defaults to [255, 255, 255, 255]. If no alpha channel is passed it is + assumed to be 255. + color : single or array of colors passed to Layer.get_fill_color + or a lonboard.basemap object, or a string to a maplibre style basemap. + vmin : Minimum value for color mapping. + vmax : Maximum value for color mapping. + wireframe : Whether to use wireframe styling in deckgl. + tiles : Either a known string {"CartoDB Positron", + "CartoDB Positron No Label", "CartoDB Darkmatter", + "CartoDB Darkmatter No Label", "CartoDB Voyager", + "CartoDB Voyager No Label"} + highlight : Whether to highlight each feature on mouseover (passed to + lonboard.Layer's auto_highlight). Defaults to False. + m: An existing Map object to plot onto. + + Returns: + lonboard.Map + a lonboard map with geodataframe included as a Layer object. + + """ + return _dexplore( + self._obj, + column=column, + cmap=cmap, + scheme=scheme, + k=k, + categorical=categorical, + elevation=elevation, + elevation_scale=elevation_scale, + alpha=alpha, + layer_kwargs=layer_kwargs, + map_kwargs=map_kwargs, + classification_kwds=classification_kwds, + nan_color=nan_color, + color=color, + vmin=vmin, + vmax=vmax, + wireframe=wireframe, + tiles=tiles, + highlight=highlight, + m=m, + ) + + +def _dexplore( # noqa: C901, PLR0912, PLR0913, PLR0915 + gdf, # noqa: ANN001 + *, + column, # noqa: ANN001 + cmap, # noqa: ANN001 + scheme, # noqa: ANN001 + k, # noqa: ANN001 + categorical, # noqa: ANN001 + elevation, # noqa: ANN001 + elevation_scale, # noqa: ANN001 + alpha, # noqa: ANN001 + layer_kwargs, # noqa: ANN001 + map_kwargs, # noqa: ANN001 + classification_kwds, # noqa: ANN001 + nan_color, # noqa: ANN001 + color, # noqa: ANN001 + vmin, # noqa: ANN001 + vmax, # noqa: ANN001 + wireframe, # noqa: ANN001 + tiles, # noqa: ANN001 + highlight, # noqa: ANN001 + m, # noqa: ANN001 +) -> Map: + """Explore a dataframe using lonboard and deckgl. + + See the public docstring for detailed parameter information + + Returns + ------- + lonboard.Map + a lonboard map with geodataframe included as a Layer object. + + """ + if map_kwargs is None: + map_kwargs = {} + if classification_kwds is None: + classification_kwds = {} + if layer_kwargs is None: + layer_kwargs = {} + if isinstance(elevation, str): + if elevation in gdf.columns: + elevation = gdf[elevation] + else: + raise ValueError( + f"the designated height column {elevation} is not in the dataframe", + ) + if not pd.api.types.is_numeric_dtype(elevation): + raise ValueError("elevation must be a numeric data type") + if elevation is not None: + layer_kwargs["extruded"] = True + if nan_color is None: + nan_color = [255, 255, 255, 255] + if not pd.api.types.is_list_like(nan_color): + raise ValueError("nan_color must be an iterable of 3 or 4 values") + if len(nan_color) != 4: + if len(nan_color) == 3: + nan_color = np.append(nan_color, [255]) + else: + raise ValueError("nan_color must be an iterable of 3 or 4 values") + + # only polygons have z + if ["Polygon", "MultiPolygon"] in gdf.geometry.geom_type.unique(): + layer_kwargs["get_elevation"] = elevation + layer_kwargs["elevation_scale"] = elevation_scale + layer_kwargs["wireframe"] = wireframe + layer_kwargs["auto_highlight"] = highlight + + line = False # set color of lines, not fill_color + if ["LineString", "MultiLineString"] in gdf.geometry.geom_type.unique(): + line = True + if color: + if line: + layer_kwargs["get_color"] = color + else: + layer_kwargs["get_fill_color"] = color + if column is not None: + try: + from matplotlib import colormaps + except ImportError as e: + raise ImportError( + "you must have matplotlib installed to style by a column", + ) from e + + if column not in gdf.columns: + raise ValueError(f"the designated column {column} is not in the dataframe") + if gdf[column].dtype in ["O", "category"]: + categorical = True + if cmap is not None and cmap not in colormaps: + raise ValueError( + f"`cmap` must be one of {list(colormaps.keys())} but {cmap} was passed", + ) + if cmap is None: + cmap = "tab20" if categorical else "viridis" + if categorical: + color_array = _get_categorical_cmap(gdf[column], cmap, nan_color, alpha) + elif scheme is None: + if vmin is None: + vmin = np.nanmin(gdf[column]) + if vmax is None: + vmax = np.nanmax(gdf[column]) + # minmax scale the column first, matplotlib needs 0-1 + transformed = (gdf[column] - vmin) / (vmax - vmin) + color_array = apply_continuous_cmap( + values=transformed, + cmap=colormaps[cmap], + alpha=alpha, + ) + else: + try: + from mapclassify._classify_API import _classifiers + from mapclassify.util import get_color_array + + _klasses = list(_classifiers.keys()) + _klasses.append("userdefined") + except ImportError as e: + raise ImportError( + "you must have the `mapclassify` package installed to use the " + "`scheme` keyword", + ) from e + if scheme.replace("_", "") not in _klasses: + raise ValueError( + "the classification scheme must be a valid mapclassify" + f"classifier in {_klasses}," + f"but {scheme} was passed instead", + ) + if k is not None and "k" in classification_kwds: + # k passed directly takes precedence + classification_kwds.pop("k") + color_array = get_color_array( + gdf[column], + scheme=scheme, + k=k, + cmap=cmap, + alpha=alpha, + nan_color=nan_color, + **classification_kwds, + ) + + if line: + layer_kwargs["get_color"] = color_array + + else: + layer_kwargs["get_fill_color"] = color_array + if tiles: + map_kwargs["basemap_style"] = _query_name(tiles) + new_m = viz( + gdf, + polygon_kwargs=layer_kwargs, + scatterplot_kwargs=layer_kwargs, + path_kwargs=layer_kwargs, + map_kwargs=map_kwargs, + ) + if m is not None: + new_m = m.add_layer(new_m) + + return new_m + + +def _get_categorical_cmap(categories, cmap, nan_color, alpha): # noqa: ANN001, ANN202 + try: + from matplotlib import colormaps + except ImportError as e: + raise ImportError( + "this function requires the `lonboard` package to be installed", + ) from e + + cat_codes = pd.Series(pd.Categorical(categories).codes, dtype="category") + # nans are encoded as -1 OR largest category depending on input type + # re-encode to always be last category + cat_codes = cat_codes.cat.rename_categories({-1: len(cat_codes.unique()) - 1}) + unique_cats = categories.dropna().unique() + n_cats = len(unique_cats) + colors = colormaps[cmap].resampled(n_cats)(list(range(n_cats)), alpha, bytes=True) + colors = np.vstack([colors, nan_color]) + temp_cmap = dict(zip(range(n_cats + 1), colors)) + return apply_categorical_cmap(cat_codes, temp_cmap) + +def _query_name(name: str) -> basemap: + """Return basemap URL based on the name query (mimicking behavior from xyzservices). + + Returns a matching basemap from name contains the same letters in the same + order as the provider's name irrespective of the letter case, spaces, dashes + and other characters. See examples for details. + + Parameters + ---------- + name : str + Name of the tile provider. Formatting does not matter. + + Returns + ------- + match: lonboard.basemap + + Examples + -------- + >>> import xyzservices.providers as xyz + + All these queries return the same ``CartoDB.Positron`` TileProvider: + + >>> xyz._query_name("CartoDB Positron") + >>> xyz._query_name("cartodbpositron") + >>> xyz._query_name("cartodb-positron") + >>> xyz._query_name("carto db/positron") + >>> xyz._query_name("CARTO_DB_POSITRON") + >>> xyz._query_name("CartoDB.Positron") + + """ + providers = { + "CartoDB Positron": basemap.CartoBasemap.Positron, + "CartoDB Positron No Label": basemap.CartoBasemap.PositronNoLabels, + "CartoDB Darkmatter": basemap.CartoBasemap.DarkMatter, + "CartoDB Darkmatter No Label": basemap.CartoBasemap.DarkMatterNoLabels, + "CartoDB Voyager": basemap.CartoBasemap.Voyager, + "CartoDB Voyager No Label": basemap.CartoBasemap.VoyagerNoLabels, + } + xyz_flat_lower = { + k.translate(QUERY_NAME_TRANSLATION).lower(): v + for k, v in providers.items() + } + name_clean = name.translate(QUERY_NAME_TRANSLATION).lower() + if name_clean in xyz_flat_lower: + return xyz_flat_lower[name_clean] + + raise ValueError(f"No matching provider found for the query '{name}'.") diff --git a/pyproject.toml b/pyproject.toml index b58adda8..f43334b1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -78,6 +78,7 @@ dev = [ "geoarrow-rust-core>=0.3.0", "geodatasets>=2024.8.0", "jupyterlab>=4.3.3", + "mapclassify>=2.8.1", "matplotlib>=3.7.5", "movingpandas>=0.20.0", "palettable>=3.3.3",