diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aae9a99..f3fed16 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,6 +2,8 @@ name: Tests on: push: + branches: + - master pull_request: branches: - master diff --git a/docs/source/api_reference/basemap/basemap.rst b/docs/source/api_reference/basemap/basemap.rst index 76e3f8d..a22667e 100644 --- a/docs/source/api_reference/basemap/basemap.rst +++ b/docs/source/api_reference/basemap/basemap.rst @@ -9,4 +9,5 @@ These are the various basemaps supported by ``here-map-widget-for-jupyter`` japan raster vector - map_tile \ No newline at end of file + map_tile + external_basemap \ No newline at end of file diff --git a/docs/source/api_reference/basemap/external_basemap.rst b/docs/source/api_reference/basemap/external_basemap.rst new file mode 100644 index 0000000..4f172e0 --- /dev/null +++ b/docs/source/api_reference/basemap/external_basemap.rst @@ -0,0 +1,33 @@ +External Basemaps +================= +``here-map-widget-for-jupyter`` also supports basemaps from external tile providers. +We use tile providers defined in `xyzservices `_ +:attr:`here_map_widget.basemaps` is mapped to :class:`xyzservices.providers` + + +Example +------- + +.. jupyter-execute:: + + import os + from here_map_widget import Map, basemaps + + m = Map( + api_key=os.environ["LS_API_KEY"], + center=[52.51477270923461, 13.39846691425174], + zoom=4, + basemap=basemaps.OpenStreetMap.Mapnik, + ) + m + + + +Attributes +---------- + +=================== ============================================================ === +Attribute Type Doc +=================== ============================================================ === +basemap object The basemap is object of :class:`xyzservices.lib.TileProvider`, defined under :attr:`here_map_widget.basemaps`. +=================== ============================================================ === \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index cdbf189..27d9c32 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -43,6 +43,7 @@ For full list of functionalities please see below sections. api_reference/basemap/raster api_reference/basemap/vector api_reference/basemap/map_tile + api_reference/basemap/external_basemap .. toctree:: :caption: Layers diff --git a/examples/basemaps.ipynb b/examples/basemaps.ipynb index 72e7979..40ee044 100644 --- a/examples/basemaps.ipynb +++ b/examples/basemaps.ipynb @@ -20,14 +20,19 @@ "os.environ[\"LS_API_KEY\"] = \"MY-API-KEY\" # replace your API key here." ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Default Basemap Example" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Default Basemap Example\n", - "\n", "from here_map_widget import Map\n", "import os\n", "\n", @@ -47,14 +52,19 @@ "m.bounds = (42.3472, 42.3736, -71.0408, -71.0751) # South, West, North, East" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Default Basemap with dark style" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Default Basemap with dark style\n", - "\n", "from here_map_widget import Map, Style\n", "import os\n", "\n", @@ -72,14 +82,19 @@ "m" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Default layers raster basemap" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# Default layers raster basemap\n", - "\n", "from here_map_widget import Map, DefaultLayers, DefaultLayerNames, Platform\n", "import os\n", "\n", @@ -101,14 +116,19 @@ "m.basemap = DefaultLayers(layer_name=DefaultLayerNames.raster.terrain.map)" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## HERE OMV service data as basemap" + ] + }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "# HERE OMV service data as basemap\n", - "\n", "from here_map_widget import Map, OMV, Platform, Style, TileLayer\n", "from here_map_widget import ServiceNames, OMVUrl\n", "import os\n", @@ -141,6 +161,13 @@ "m" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## HERE Maptile service data as basemap" + ] + }, { "cell_type": "code", "execution_count": null, @@ -149,8 +176,6 @@ }, "outputs": [], "source": [ - "# HERE Maptile service data as basemap\n", - "\n", "from here_map_widget import Map, Platform, MapTile, TileLayer\n", "from here_map_widget import ServiceNames, MapTileUrl\n", "import os\n", @@ -184,6 +209,52 @@ ")\n", "m" ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## External basemaps\n", + "Basemaps from other tile providers are also supported.\n", + "[xyzservices](https://github.com/geopandas/xyzservices) supports all the external tile providers in single place and has easy to use \n", + "python API. External basemaps in here-map-widget-for-jupyter are mapped to `xyzservices.providers`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from here_map_widget import basemaps" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "basemaps.OpenStreetMap.Mapnik" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os\n", + "from here_map_widget import Map\n", + "\n", + "m = Map(\n", + " api_key=os.environ[\"LS_API_KEY\"],\n", + " center=[52.51477270923461, 13.39846691425174],\n", + " zoom=4,\n", + " basemap=basemaps.OpenStreetMap.Mapnik,\n", + ")\n", + "m" + ] } ], "metadata": { diff --git a/here_map_widget/__init__.py b/here_map_widget/__init__.py index 8521632..9cb2585 100644 --- a/here_map_widget/__init__.py +++ b/here_map_widget/__init__.py @@ -7,6 +7,8 @@ to make analysis ofgeospatial data easier and more interactive. """ +import xyzservices.providers as basemaps # noqa E501 + from ._version import __version__, version_info from .configs import * from .map import * diff --git a/here_map_widget/map.py b/here_map_widget/map.py index d3e2fcc..ef73e74 100644 --- a/here_map_widget/map.py +++ b/here_map_widget/map.py @@ -8,6 +8,7 @@ import copy import json +import xyzservices from branca.colormap import ColorMap, linear from ipywidgets import ( Box, @@ -42,6 +43,29 @@ def_loc = [20.5937, 78.9629] +def basemap_to_tiles(basemap, opacity=1, **kwargs): + """Turn a basemap into a TileLayer object. + + Parameters + ---------- + basemap : class:`xyzservices.lib.TileProvider` + Basemap description coming from here_map_widget.basemaps. + opacity: Int, Optional + Opacity of the TileLayer, default 1. + kwargs: key-word arguments + Extra key-word arguments to pass to the ``basemap.build_url`` method. + """ + url = basemap.build_url(**kwargs) + provider = ImageTileProvider( + url=url, + max_zoom=basemap.get("max_zoom", 19), + min_zoom=basemap.get("min_zoom", 1), + attribution=basemap.get("html_attribution", ""), + opacity=opacity, + ) + return TileLayer(provider=provider, name=basemap.get("name", "")) + + class InteractMixin(object): """Abstract InteractMixin class.""" @@ -1936,17 +1960,45 @@ class Map(DOMWidget, InteractMixin): ) basemap = Union( ( - Instance(DefaultLayers, default_value=None, allow_none=True), - Instance(TileLayer, default_value=None, allow_none=True), - Instance(MapTile, default_value=None, allow_none=True), - ) + Instance(DefaultLayers), + Instance(TileLayer), + Instance(MapTile), + Instance(xyzservices.lib.TileProvider), + ), + default_value=DefaultLayers(layer_name=DefaultLayerNames.vector.normal.map), ).tag(sync=True, **widget_serialization) + zoom_control = Bool(True) + _view_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) _model_module_version = Unicode(EXTENSION_VERSION).tag(sync=True) _layer_ids = List() + def __init__(self, api_key, **kwargs): + self.zoom_control_instance = None + self.api_key = api_key + super().__init__(**kwargs) + if self.zoom_control: + self.zoom_control_instance = ZoomControl(alignment="LEFT_TOP") + self.add_control(self.zoom_control_instance) + if isinstance(self.basemap, xyzservices.lib.TileProvider): + if "HEREv3" in self.basemap.name: + self.basemap["apiKey"] = self.api_key + self.basemap = basemap_to_tiles(self.basemap) + + @observe("zoom_control") + def observe_zoom_control(self, change): + if change["new"]: + self.zoom_control_instance = ZoomControl(alignment="LEFT_TOP") + self.add_control(self.zoom_control_instance) + else: + if ( + self.zoom_control_instance is not None + and self.zoom_control_instance in self.controls + ): + self.remove_control(self.zoom_control_instance) + @validate("layers") def _validate_layers(self, proposal): """Validate layers list. diff --git a/requirements.txt b/requirements.txt index 8ec4386..19a353e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,3 @@ ipywidgets>=7.6.0,<8 -branca>=0.3.1,<0.5 \ No newline at end of file +branca>=0.3.1,<0.5 +xyzservices>=2021.8.0 \ No newline at end of file