diff --git a/docs/workshops/HGAC_2024.ipynb b/docs/workshops/HGAC_2024.ipynb new file mode 100644 index 0000000000..cf49c89712 --- /dev/null +++ b/docs/workshops/HGAC_2024.ipynb @@ -0,0 +1,4673 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "0", + "metadata": {}, + "source": [ + "[![image](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/opengeos/leafmap/blob/master/docs/workshops/HGAC_2024.ipynb)\n", + "\n", + "**3D Mapping with MapLibre and Leafmap**\n", + "\n", + "## Introduction\n", + "\n", + "This notebook is for the workshop presented at the [Houston Area GIS Day Workshops](https://www.h-gac.com/events/houston-area-gis-day-workshops).\n", + "\n", + "Attendees will learn how to create 3D maps using MapLibre and Leafmap. Topics include:\n", + "\n", + "* Create interactive 2D maps with ipyleaflet\n", + "* Create interactive 3D maps with MapLibre\n", + "* Visualize 3D terrain, 3D buildings, and 3D indoor mapping\n", + "* Visualize local and cloud-based geospatial data, including COG, STAC, and PMTiles\n", + "* Add custom map components\n", + "* Export 3D maps as HTML for seamless website integration\n", + "\n", + "## Useful Resources\n", + "\n", + "- [MapLibre GL JS Documentation](https://maplibre.org/maplibre-gl-js/docs): Comprehensive documentation for MapLibre GL JS.\n", + "- [MapLibre Python Bindings](https://github.com/eoda-dev/py-maplibregl): Information on using MapLibre with Python.\n", + "- [MapLibre in Leafmap](https://leafmap.org/maplibre/overview): Examples and tutorials for MapLibre in Leafmap.\n", + "- [Video Tutorials](https://bit.ly/maplibre): Video guides for practical MapLibre skills.\n", + "- [MapLibre Demos](https://maps.gishub.org): Interactive demos showcasing MapLibre’s capabilities.\n", + "\n", + "\n", + "## Installation and Setup\n", + "\n", + "To install the required packages, uncomment and run the line below." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1", + "metadata": {}, + "outputs": [], + "source": [ + "# %pip install -U \"leafmap[maplibre]\"" + ] + }, + { + "cell_type": "markdown", + "id": "2", + "metadata": {}, + "source": [ + "Once installed, import the `maplibregl` backend from the `leafmap` package:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "3", + "metadata": {}, + "outputs": [], + "source": [ + "import leafmap.maplibregl as leafmap" + ] + }, + { + "cell_type": "markdown", + "id": "4", + "metadata": {}, + "source": [ + "## Creating Interactive Maps\n", + "\n", + "### Basic Map Setup\n", + "\n", + "Let’s start by creating a simple interactive map with default settings. This basic setup provides simple map with the `dark-matter` style on which you can add data layers, controls, and other customizations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "6", + "metadata": {}, + "source": [ + "### Customizing the Map's Center and Zoom Level\n", + "\n", + "You can specify the map’s center (latitude and longitude), zoom level, pitch, and bearing for a more focused view. These parameters help direct the user's attention to specific areas." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "7", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, pitch=0, bearing=0)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "8", + "metadata": {}, + "source": [ + "Hold down the `Ctrl` key. Click and drag to pan the map." + ] + }, + { + "cell_type": "markdown", + "id": "9", + "metadata": {}, + "source": [ + "### Choosing a Basemap Style\n", + "\n", + "MapLibre supports several pre-defined basemap styles such as `dark-matter`, `positron`, `voyager`, `liberty`, `demotiles`. You can also use custom basemap URLs for unique styling." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "10", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"positron\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "11", + "metadata": {}, + "source": [ + "[OpenFreeMap](https://openfreemap.org) provides a variety of basemap styles that you can use in your interactive maps. These styles include `liberty`, `bright`, and `positron`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "12", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "13", + "metadata": {}, + "source": [ + "### Adding Background Colors\n", + "\n", + "Background color styles, such as `background-lightgray` and `background-green`, are helpful for simple or thematic maps where color-coding is needed." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "14", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"background-lightgray\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "15", + "metadata": {}, + "source": [ + "Alternatively, you can provide a URL to a vector style." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "16", + "metadata": {}, + "outputs": [], + "source": [ + "style = \"https://demotiles.maplibre.org/style.json\"\n", + "m = leafmap.Map(style=style)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "17", + "metadata": {}, + "source": [ + "## Adding Map Controls\n", + "\n", + "Map controls enhance the usability of the map by allowing users to interact in various ways, adding elements like scale bars, zoom tools, and drawing options.\n", + "\n", + "### Available Controls\n", + "\n", + "- **Geolocate**: Centers the map based on the user’s current location, if available.\n", + "- **Fullscreen**: Expands the map to a full-screen view for better focus.\n", + "- **Navigation**: Provides zoom controls and a compass for reorientation.\n", + "- **Draw**: Allows users to draw and edit shapes on the map.\n", + "\n", + "### Adding Geolocate Control\n", + "\n", + "The Geolocate control centers the map based on the user’s current location, a helpful feature for location-based applications." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "18", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_control(\"geolocate\", position=\"top-left\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "19", + "metadata": {}, + "source": [ + "### Adding Fullscreen Control\n", + "\n", + "Fullscreen control enables users to expand the map to full screen, enhancing focus and visual clarity. This is especially useful when viewing complex or large datasets." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "20", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[11.255, 43.77], zoom=13, style=\"positron\", controls={})\n", + "m.add_control(\"fullscreen\", position=\"top-right\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "21", + "metadata": {}, + "source": [ + "### Adding Navigation Control\n", + "\n", + "The Navigation control provides buttons for zooming and reorienting the map, improving the user's ability to navigate efficiently." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "22", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[11.255, 43.77], zoom=13, style=\"positron\", controls={})\n", + "m.add_control(\"navigation\", position=\"top-left\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "23", + "metadata": {}, + "source": [ + "### Adding Draw Control\n", + "\n", + "The Draw control enables users to interact with the map by adding shapes such as points, lines, and polygons. This control is essential for tasks requiring spatial data input directly on the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "24", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_draw_control(position=\"top-left\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "25", + "metadata": {}, + "source": [ + "You can configure the Draw control to activate specific tools, like polygon, line, or point drawing, while also enabling or disabling a \"trash\" button to remove unwanted shapes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "26", + "metadata": {}, + "outputs": [], + "source": [ + "from maplibre.plugins import MapboxDrawControls, MapboxDrawOptions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "27", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "draw_options = MapboxDrawOptions(\n", + " display_controls_default=False,\n", + " controls=MapboxDrawControls(polygon=True, line_string=True, point=True, trash=True),\n", + ")\n", + "m.add_draw_control(draw_options)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "28", + "metadata": {}, + "source": [ + "Additionally, you can load a GeoJSON FeatureCollection into the Draw control, which will allow users to view, edit, or interact with pre-defined geographical features, such as boundaries or points of interest." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "29", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "geojson = {\n", + " \"type\": \"FeatureCollection\",\n", + " \"features\": [\n", + " {\n", + " \"id\": \"abc\",\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\n", + " \"coordinates\": [\n", + " [\n", + " [-119.08, 45.95],\n", + " [-119.79, 42.08],\n", + " [-107.28, 41.43],\n", + " [-108.15, 46.44],\n", + " [-119.08, 45.95],\n", + " ]\n", + " ],\n", + " \"type\": \"Polygon\",\n", + " },\n", + " },\n", + " {\n", + " \"id\": \"xyz\",\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\n", + " \"coordinates\": [\n", + " [\n", + " [-103.87, 38.08],\n", + " [-108.54, 36.44],\n", + " [-106.25, 33.00],\n", + " [-99.91, 31.79],\n", + " [-96.82, 35.48],\n", + " [-98.80, 37.77],\n", + " [-103.87, 38.08],\n", + " ]\n", + " ],\n", + " \"type\": \"Polygon\",\n", + " },\n", + " },\n", + " ],\n", + "}\n", + "m.add_draw_control(position=\"top-left\", geojson=geojson)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "30", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/w8UFssd.png)" + ] + }, + { + "cell_type": "markdown", + "id": "31", + "metadata": {}, + "source": [ + "Two key methods for accessing drawn features:\n", + "\n", + "- **Selected Features**: Accesses only the currently selected features.\n", + "- **All Features**: Accesses all features added, regardless of selection, giving you full control over the spatial data on the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "32", + "metadata": {}, + "outputs": [], + "source": [ + "m.draw_features_selected" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "33", + "metadata": {}, + "outputs": [], + "source": [ + "m.draw_feature_collection_all" + ] + }, + { + "cell_type": "markdown", + "id": "34", + "metadata": {}, + "source": [ + "## Adding Layers\n", + "\n", + "Adding layers to a map enhances the data it presents, allowing different types of basemaps, tile layers, and thematic overlays to be combined for in-depth analysis.\n", + "\n", + "### Adding Basemaps\n", + "\n", + "Basemaps provide a geographical context for the map. Using the `add_basemap` method, you can select from various basemaps, including `OpenTopoMap` and `Esri.WorldImagery`. Adding a layer control allows users to switch between multiple basemaps interactively." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "35", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m.add_basemap(\"OpenTopoMap\")\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "36", + "metadata": {}, + "outputs": [], + "source": [ + "m.add_basemap(\"Esri.WorldImagery\")" + ] + }, + { + "cell_type": "markdown", + "id": "37", + "metadata": {}, + "source": [ + "You can also add basemaps interactively, which provides flexibility for selecting the best background for your map content." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "38", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "39", + "metadata": {}, + "outputs": [], + "source": [ + "m.add_basemap()" + ] + }, + { + "cell_type": "markdown", + "id": "40", + "metadata": {}, + "source": [ + "### Adding XYZ Tile Layer\n", + "\n", + "XYZ tile layers allow integration of specific tile services like topographic, satellite, or other thematic imagery from XYZ tile servers. By specifying the URL and parameters such as `opacity` and `visibility`, XYZ layers can be customized and styled to fit the needs of your map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "41", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "url = \"https://basemap.nationalmap.gov/arcgis/rest/services/USGSTopo/MapServer/tile/{z}/{y}/{x}\"\n", + "m.add_tile_layer(url, name=\"USGS TOpo\", attribution=\"USGS\", opacity=1.0, visible=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "42", + "metadata": {}, + "source": [ + "### Adding WMS Layer\n", + "\n", + "Web Map Service (WMS) layers provide access to external datasets served by web servers, such as thematic maps or detailed satellite imagery. Adding a WMS layer involves specifying the WMS URL and layer names, which allows for overlaying various data types such as natural imagery or land cover classifications." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "43", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-74.5447, 40.6892], zoom=8, style=\"liberty\")\n", + "url = \"https://img.nj.gov/imagerywms/Natural2015\"\n", + "layers = \"Natural2015\"\n", + "m.add_wms_layer(url, layers=layers, before_id=\"aeroway_fill\")\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "44", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100.307965, 46.98692], zoom=13, pitch=45, style=\"liberty\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer\"\n", + "m.add_wms_layer(url, layers=\"1\", name=\"NWI\", opacity=0.6)\n", + "m.add_layer_control()\n", + "m.add_legend(builtin_legend=\"NWI\", title=\"Wetland Type\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "45", + "metadata": {}, + "source": [ + "### Adding Raster Tiles\n", + "\n", + "Raster tiles provide a set of customized styles for map layers, such as watercolor or artistic styles, which can add unique visual elements. Configuring raster tile styles involves setting tile URLs, tile size, and attribution information. This setup provides access to a wide array of predefined designs, offering creative flexibility in how data is presented.\n", + "\n", + "With raster tiles, you can control zoom limits and apply unique visual IDs to distinguish between multiple raster sources on the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "46", + "metadata": {}, + "outputs": [], + "source": [ + "style = {\n", + " \"version\": 8,\n", + " \"sources\": {\n", + " \"raster-tiles\": {\n", + " \"type\": \"raster\",\n", + " \"tiles\": [\n", + " \"https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg\"\n", + " ],\n", + " \"tileSize\": 256,\n", + " \"attribution\": \"Map tiles by Stamen Design; Hosting by Stadia Maps. Data © OpenStreetMap contributors\",\n", + " }\n", + " },\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"simple-tiles\",\n", + " \"type\": \"raster\",\n", + " \"source\": \"raster-tiles\",\n", + " \"minzoom\": 0,\n", + " \"maxzoom\": 22,\n", + " }\n", + " ],\n", + "}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "47", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-74.5, 40], zoom=2, style=style)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "48", + "metadata": {}, + "source": [ + "## MapTiler\n", + "\n", + "To use MapTiler with this notebook, you need to set up a MapTiler API key. You can obtain a free API key by signing up at [https://cloud.maptiler.com/](https://cloud.maptiler.com)." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "49", + "metadata": {}, + "outputs": [], + "source": [ + "# import os\n", + "# os.environ[\"MAPTILER_KEY\"] = \"YOUR_API_KEY\"" + ] + }, + { + "cell_type": "markdown", + "id": "50", + "metadata": {}, + "source": [ + "Set the API key as an environment variable to access MapTiler's resources. Once set up, you can specify any named style from MapTiler by using the style parameter in the map setup.\n", + "\n", + "![](https://i.imgur.com/dp2HxR2.png)\n", + "\n", + "The following are examples of different styles available through MapTiler:\n", + "\n", + "- **Streets style**: This style displays detailed street information and is suited for urban data visualization.\n", + "- **Satellite style**: Provides high-resolution satellite imagery for various locations.\n", + "- **Hybrid style**: Combines satellite imagery with labels for a clear geographic context.\n", + "- **Topo style**: A topographic map showing terrain details, ideal for outdoor-related applications." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "51", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "52", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"satellite\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "53", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"hybrid\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "54", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"topo\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "55", + "metadata": {}, + "source": [ + "### Adding a vector tile source\n", + "\n", + "To include vector data from MapTiler, first obtain the MapTiler API key and then set up a vector source with the desired tile data URL. The vector tile source can then be added to a layer to display features such as contour lines, which are styled for better visibility and engagement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "56", + "metadata": {}, + "outputs": [], + "source": [ + "MAPTILER_KEY = leafmap.get_api_key(\"MAPTILER_KEY\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "57", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-122.447303, 37.753574], zoom=13, style=\"streets\")\n", + "source = {\n", + " \"type\": \"vector\",\n", + " \"url\": f\"https://api.maptiler.com/tiles/contours/tiles.json?key={MAPTILER_KEY}\",\n", + "}\n", + "layer = {\n", + " \"id\": \"terrain-data\",\n", + " \"type\": \"line\",\n", + " \"source\": \"contours\",\n", + " \"source-layer\": \"contour\",\n", + " \"layout\": {\"line-join\": \"round\", \"line-cap\": \"round\"},\n", + " \"paint\": {\"line-color\": \"#ff69b4\", \"line-width\": 1},\n", + "}\n", + "m.add_source(\"contours\", source)\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "58", + "metadata": {}, + "source": [ + "## 3D Mapping\n", + "\n", + "MapTiler provides a variety of 3D styles that enhance geographic data visualization. Styles can be prefixed with `3d-` to utilize 3D effects such as hybrid, satellite, and topo. The `3d-hillshade` style is used for visualizing hillshade effects alone.\n", + "\n", + "### 3D Terrain\n", + "\n", + "These examples demonstrate different ways to implement 3D terrain visualization:\n", + "\n", + "- **3D Hybrid style**: Adds terrain relief to urban data with hybrid visualization.\n", + "- **3D Satellite style**: Combines satellite imagery with 3D elevation, enhancing visual context for topography.\n", + "- **3D Topo style**: Provides a topographic view with elevation exaggeration and optional hillshade.\n", + "- **3D Terrain style**: Displays terrain with default settings for natural geographic areas.\n", + "- **3D Ocean style**: A specialized terrain style with oceanic details, using exaggeration and hillshade to emphasize depth.\n", + "\n", + "Each terrain map setup includes a pitch and bearing to adjust the map's angle and orientation, giving a better perspective of 3D features." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "59", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.1874314, 46.2022386], zoom=13, pitch=60, bearing=220, style=\"3d-hybrid\"\n", + ")\n", + "m.add_layer_control(bg_layers=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "60", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/3Q2Q3CG.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.1874314, 46.2022386],\n", + " zoom=13,\n", + " pitch=60,\n", + " bearing=220,\n", + " style=\"3d-satellite\",\n", + ")\n", + "m.add_layer_control(bg_layers=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "62", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/5PNMbAv.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "63", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.1874314, 46.2022386],\n", + " zoom=13,\n", + " pitch=60,\n", + " bearing=220,\n", + " style=\"3d-topo\",\n", + " exaggeration=1.5,\n", + " hillshade=False,\n", + ")\n", + "m.add_layer_control(bg_layers=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "64", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/y33leIj.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "65", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.1874314, 46.2022386],\n", + " zoom=13,\n", + " pitch=60,\n", + " bearing=220,\n", + " style=\"3d-terrain\",\n", + ")\n", + "m.add_layer_control(bg_layers=True)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "66", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"3d-ocean\", exaggeration=1.5, hillshade=True)\n", + "m.add_layer_control(bg_layers=True)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "67", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/m6NwSWG.png)" + ] + }, + { + "cell_type": "markdown", + "id": "68", + "metadata": {}, + "source": [ + "### 3D Buildings\n", + "\n", + "Adding 3D buildings enhances urban visualizations, showing buildings with height variations. The setup involves specifying the MapTiler API key for vector tiles and adding building data as a 3D extrusion layer. The extrusion height and color can be set based on data attributes to visualize structures with varying heights, which can be useful in city planning and urban analysis." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "69", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-74.01201, 40.70473], zoom=16, pitch=60, bearing=35, style=\"basic-v2\"\n", + ")\n", + "MAPTILER_KEY = leafmap.get_api_key(\"MAPTILER_KEY\")\n", + "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n", + "source = {\n", + " \"url\": f\"https://api.maptiler.com/tiles/v3/tiles.json?key={MAPTILER_KEY}\",\n", + " \"type\": \"vector\",\n", + "}\n", + "\n", + "layer = {\n", + " \"id\": \"3d-buildings\",\n", + " \"source\": \"openmaptiles\",\n", + " \"source-layer\": \"building\",\n", + " \"type\": \"fill-extrusion\",\n", + " \"min-zoom\": 15,\n", + " \"paint\": {\n", + " \"fill-extrusion-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"get\", \"render_height\"],\n", + " 0,\n", + " \"lightgray\",\n", + " 200,\n", + " \"royalblue\",\n", + " 400,\n", + " \"lightblue\",\n", + " ],\n", + " \"fill-extrusion-height\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"zoom\"],\n", + " 15,\n", + " 0,\n", + " 16,\n", + " [\"get\", \"render_height\"],\n", + " ],\n", + " \"fill-extrusion-base\": [\n", + " \"case\",\n", + " [\">=\", [\"get\", \"zoom\"], 16],\n", + " [\"get\", \"render_min_height\"],\n", + " 0,\n", + " ],\n", + " },\n", + "}\n", + "m.add_source(\"openmaptiles\", source)\n", + "m.add_layer(layer)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "70", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/9QeicaE.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "71", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-74.01201, 40.70473], zoom=16, pitch=60, bearing=35, style=\"basic-v2\"\n", + ")\n", + "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n", + "m.add_3d_buildings(min_zoom=15)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "72", + "metadata": {}, + "source": [ + "### 3D Indoor Mapping\n", + "\n", + "Indoor mapping data can be visualized by loading a GeoJSON file and applying the `add_geojson` method. This setup allows for displaying floorplans with attributes such as color, height, and opacity. It provides a realistic indoor perspective, which is useful for visualizing complex structures or navigating interior spaces." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73", + "metadata": {}, + "outputs": [], + "source": [ + "data = \"https://maplibre.org/maplibre-gl-js/docs/assets/indoor-3d-map.geojson\"\n", + "gdf = leafmap.geojson_to_gdf(data)\n", + "gdf.explore()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "74", + "metadata": {}, + "outputs": [], + "source": [ + "gdf.head()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "75", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=(-87.61694, 41.86625), zoom=17, pitch=40, bearing=20, style=\"positron\"\n", + ")\n", + "m.add_basemap(\"OpenStreetMap.Mapnik\")\n", + "m.add_geojson(\n", + " data,\n", + " layer_type=\"fill-extrusion\",\n", + " name=\"floorplan\",\n", + " paint={\n", + " \"fill-extrusion-color\": [\"get\", \"color\"],\n", + " \"fill-extrusion-height\": [\"get\", \"height\"],\n", + " \"fill-extrusion-base\": [\"get\", \"base_height\"],\n", + " \"fill-extrusion-opacity\": 0.5,\n", + " },\n", + ")\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "76", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/eYhSWaT.png)" + ] + }, + { + "cell_type": "markdown", + "id": "77", + "metadata": {}, + "source": [ + "### 3D Choropleth Map\n", + "\n", + "Choropleth maps in 3D allow visualizing attribute data by height, where regions are colored and extruded based on specific attributes, such as age or population area. Two examples are provided below:\n", + "\n", + "- **European Countries (Age at First Marriage)**: The map displays different colors and extrusion heights based on age data. Color interpolations help represent data variations across regions, making it a suitable map for demographic studies.\n", + "\n", + "- **US Counties (Census Area)**: This example uses census data to display county areas in the United States, where each county’s area is represented with a different color and height based on its size. This type of map provides insights into geographic distribution and area proportions across the region.\n", + "\n", + "Both maps use a fill-extrusion style to dynamically adjust color and height, creating a 3D effect that enhances data interpretation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "78", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[19.43, 49.49], zoom=3, pitch=60, style=\"basic\")\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": \"https://docs.maptiler.com/sdk-js/assets/Mean_age_of_women_at_first_marriage_in_2019.geojson\",\n", + "}\n", + "m.add_source(\"countries\", source)\n", + "layer = {\n", + " \"id\": \"eu-countries\",\n", + " \"source\": \"countries\",\n", + " \"type\": \"fill-extrusion\",\n", + " \"paint\": {\n", + " \"fill-extrusion-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"get\", \"age\"],\n", + " 23.0,\n", + " \"#fff5eb\",\n", + " 24.0,\n", + " \"#fee6ce\",\n", + " 25.0,\n", + " \"#fdd0a2\",\n", + " 26.0,\n", + " \"#fdae6b\",\n", + " 27.0,\n", + " \"#fd8d3c\",\n", + " 28.0,\n", + " \"#f16913\",\n", + " 29.0,\n", + " \"#d94801\",\n", + " 30.0,\n", + " \"#8c2d04\",\n", + " ],\n", + " \"fill-extrusion-opacity\": 1,\n", + " \"fill-extrusion-height\": [\"*\", [\"get\", \"age\"], 5000],\n", + " },\n", + "}\n", + "first_symbol_layer_id = m.find_first_symbol_layer()[\"id\"]\n", + "m.add_layer(layer, first_symbol_layer_id)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "79", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/fLgqYTa.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "80", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, pitch=60, style=\"basic\")\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": \"https://open.gishub.org/data/us/us_counties.geojson\",\n", + "}\n", + "m.add_source(\"counties\", source)\n", + "layer = {\n", + " \"id\": \"us-counties\",\n", + " \"source\": \"counties\",\n", + " \"type\": \"fill-extrusion\",\n", + " \"paint\": {\n", + " \"fill-extrusion-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"get\", \"CENSUSAREA\"],\n", + " 400,\n", + " \"#fff5eb\",\n", + " 600,\n", + " \"#fee6ce\",\n", + " 800,\n", + " \"#fdd0a2\",\n", + " 1000,\n", + " \"#fdae6b\",\n", + " ],\n", + " \"fill-extrusion-opacity\": 1,\n", + " \"fill-extrusion-height\": [\"*\", [\"get\", \"CENSUSAREA\"], 50],\n", + " },\n", + "}\n", + "first_symbol_layer_id = m.find_first_symbol_layer()[\"id\"]\n", + "m.add_layer(layer, first_symbol_layer_id)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "81", + "metadata": {}, + "source": [ + "## Visualizing Vector Data\n", + "\n", + "Leafmap provides a variety of methods to visualize vector data on a map, allowing you to display points, lines, polygons, and other vector shapes with custom styling and interactivity.\n", + "\n", + "### Point Data\n", + "\n", + "The following examples demonstrate how to display points on the map.\n", + "\n", + "- **Simple Marker**: Initializes the map centered on a specific latitude and longitude, then adds a static marker to the location.\n", + "\n", + "- **Draggable Marker**: Similar to the simple marker, but with an additional option to make the marker draggable. This allows users to reposition it directly on the map.\n", + "\n", + "- **Multiple Points with GeoJSON**: Loads point data from a GeoJSON file containing world city locations. After reading the data, it’s added to the map as a layer named “cities.” Popups can be enabled to display additional information when a user clicks on a city.\n", + "\n", + "- **Custom Symbol Layer**: Loads point data as symbols instead of markers and customizes the symbol layout using icons, which can be scaled to any size." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "82", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[12.550343, 55.665957], zoom=8, style=\"positron\")\n", + "m.add_marker(lng_lat=[12.550343, 55.665957])\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "83", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/ufmqTzx.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "84", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[12.550343, 55.665957], zoom=8, style=\"positron\")\n", + "m.add_marker(lng_lat=[12.550343, 55.665957], options={\"draggable\": True})\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "85", + "metadata": {}, + "outputs": [], + "source": [ + "url = (\n", + " \"https://github.com/opengeos/datasets/releases/download/world/world_cities.geojson\"\n", + ")\n", + "geojson = leafmap.read_geojson(url)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "86", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "m.add_geojson(geojson, name=\"cities\")\n", + "m.add_popup(\"cities\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "87", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "source = {\"type\": \"geojson\", \"data\": geojson}\n", + "\n", + "layer = {\n", + " \"id\": \"cities\",\n", + " \"type\": \"symbol\",\n", + " \"source\": \"point\",\n", + " \"layout\": {\n", + " \"icon-image\": \"marker_15\",\n", + " \"icon-size\": 1,\n", + " },\n", + "}\n", + "m.add_source(\"point\", source)\n", + "m.add_layer(layer)\n", + "m.add_popup(\"cities\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "88", + "metadata": {}, + "source": [ + "### Customizing Marker Icon Image\n", + "\n", + "To further customize point data, an image icon can be used instead of the default marker:\n", + "\n", + "- **Add Custom Icon**: Loads an OSGeo logo as the custom marker image. The GeoJSON source for conference locations is loaded, and each location is marked with the custom icon and labeled with the year. Layout options define the placement of text and icons." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "89", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[0, 0], zoom=1, style=\"positron\")\n", + "image = \"https://maplibre.org/maplibre-gl-js/docs/assets/osgeo-logo.png\"\n", + "m.add_image(\"custom-marker\", image)\n", + "\n", + "url = \"https://github.com/opengeos/datasets/releases/download/places/osgeo_conferences.geojson\"\n", + "geojson = leafmap.read_geojson(url)\n", + "\n", + "source = {\"type\": \"geojson\", \"data\": geojson}\n", + "\n", + "m.add_source(\"conferences\", source)\n", + "layer = {\n", + " \"id\": \"conferences\",\n", + " \"type\": \"symbol\",\n", + " \"source\": \"conferences\",\n", + " \"layout\": {\n", + " \"icon-image\": \"custom-marker\",\n", + " \"text-field\": [\"get\", \"year\"],\n", + " \"text-font\": [\"Open Sans Semibold\", \"Arial Unicode MS Bold\"],\n", + " \"text-offset\": [0, 1.25],\n", + " \"text-anchor\": \"top\",\n", + " },\n", + "}\n", + "\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "90", + "metadata": {}, + "source": [ + "### Line Data\n", + "\n", + "Lines can be displayed to represent routes, boundaries, or connections between locations:\n", + "\n", + "- **Basic Line**: Sets up a line with multiple coordinates to create a path. The map displays this path with rounded line joins and a defined color and width, giving it a polished appearance." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "91", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-122.486052, 37.830348], zoom=15, style=\"streets\")\n", + "\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\n", + " \"type\": \"LineString\",\n", + " \"coordinates\": [\n", + " [-122.48369693756104, 37.83381888486939],\n", + " [-122.48348236083984, 37.83317489144141],\n", + " [-122.48339653015138, 37.83270036637107],\n", + " [-122.48356819152832, 37.832056363179625],\n", + " [-122.48404026031496, 37.83114119107971],\n", + " [-122.48404026031496, 37.83049717427869],\n", + " [-122.48348236083984, 37.829920943955045],\n", + " [-122.48356819152832, 37.82954808664175],\n", + " [-122.48507022857666, 37.82944639795659],\n", + " [-122.48610019683838, 37.82880236636284],\n", + " [-122.48695850372314, 37.82931081282506],\n", + " [-122.48700141906738, 37.83080223556934],\n", + " [-122.48751640319824, 37.83168351665737],\n", + " [-122.48803138732912, 37.832158048267786],\n", + " [-122.48888969421387, 37.83297152392784],\n", + " [-122.48987674713133, 37.83263257682617],\n", + " [-122.49043464660643, 37.832937629287755],\n", + " [-122.49125003814696, 37.832429207817725],\n", + " [-122.49163627624512, 37.832564787218985],\n", + " [-122.49223709106445, 37.83337825839438],\n", + " [-122.49378204345702, 37.83368330777276],\n", + " ],\n", + " },\n", + " },\n", + "}\n", + "\n", + "layer = {\n", + " \"id\": \"route\",\n", + " \"type\": \"line\",\n", + " \"source\": \"route\",\n", + " \"layout\": {\"line-join\": \"round\", \"line-cap\": \"round\"},\n", + " \"paint\": {\"line-color\": \"#888\", \"line-width\": 8},\n", + "}\n", + "m.add_source(\"route\", source)\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "92", + "metadata": {}, + "source": [ + "### Polygon Data\n", + "\n", + "Polygons represent regions or areas on the map. Leafmap offers options to display filled polygons with customizable colors and opacities.\n", + "\n", + "- **Basic Polygon**: Adds a GeoJSON polygon representing an area and customizes its fill color and opacity.\n", + "\n", + "- **Outlined Polygon**: In addition to filling the polygon, an outline color is specified to highlight the polygon’s boundary. Both fill and line layers are defined, enhancing visual clarity.\n", + "\n", + "- **Building Visualization with GeoJSON**: Uses a URL to load building data and displays it with a customized fill color and outline. The style uses a hybrid map view for additional context." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "93", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-68.137343, 45.137451], zoom=5, style=\"streets\")\n", + "geojson = {\n", + " \"type\": \"Feature\",\n", + " \"geometry\": {\n", + " \"type\": \"Polygon\",\n", + " \"coordinates\": [\n", + " [\n", + " [-67.13734351262877, 45.137451890638886],\n", + " [-66.96466, 44.8097],\n", + " [-68.03252, 44.3252],\n", + " [-69.06, 43.98],\n", + " [-70.11617, 43.68405],\n", + " [-70.64573401557249, 43.090083319667144],\n", + " [-70.75102474636725, 43.08003225358635],\n", + " [-70.79761105007827, 43.21973948828747],\n", + " [-70.98176001655037, 43.36789581966826],\n", + " [-70.94416541205806, 43.46633942318431],\n", + " [-71.08482, 45.3052400000002],\n", + " [-70.6600225491012, 45.46022288673396],\n", + " [-70.30495378282376, 45.914794623389355],\n", + " [-70.00014034695016, 46.69317088478567],\n", + " [-69.23708614772835, 47.44777598732787],\n", + " [-68.90478084987546, 47.184794623394396],\n", + " [-68.23430497910454, 47.35462921812177],\n", + " [-67.79035274928509, 47.066248887716995],\n", + " [-67.79141211614706, 45.702585354182816],\n", + " [-67.13734351262877, 45.137451890638886],\n", + " ]\n", + " ],\n", + " },\n", + "}\n", + "source = {\"type\": \"geojson\", \"data\": geojson}\n", + "m.add_source(\"maine\", source)\n", + "layer = {\n", + " \"id\": \"maine\",\n", + " \"type\": \"fill\",\n", + " \"source\": \"maine\",\n", + " \"layout\": {},\n", + " \"paint\": {\"fill-color\": \"#088\", \"fill-opacity\": 0.8},\n", + "}\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "94", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-68.137343, 45.137451], zoom=5, style=\"streets\")\n", + "paint = {\"fill-color\": \"#088\", \"fill-opacity\": 0.8}\n", + "m.add_geojson(geojson, layer_type=\"fill\", paint=paint)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "95", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"hybrid\")\n", + "geojson = \"https://github.com/opengeos/datasets/releases/download/places/wa_overture_buildings.geojson\"\n", + "paint = {\"fill-color\": \"#ffff00\", \"fill-opacity\": 0.5, \"fill-outline-color\": \"#ff0000\"}\n", + "m.add_geojson(geojson, layer_type=\"fill\", paint=paint, name=\"Fill\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "96", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"hybrid\")\n", + "geojson = \"https://github.com/opengeos/datasets/releases/download/places/wa_overture_buildings.geojson\"\n", + "paint_line = {\"line-color\": \"#ff0000\", \"line-width\": 3}\n", + "m.add_geojson(geojson, layer_type=\"line\", paint=paint_line, name=\"Outline\")\n", + "paint_fill = {\"fill-color\": \"#ffff00\", \"fill-opacity\": 0.5}\n", + "m.add_geojson(geojson, layer_type=\"fill\", paint=paint_fill, name=\"Fill\")\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "97", + "metadata": {}, + "source": [ + "### Multiple Geometries\n", + "\n", + "This example displays both line and extrusion fills for complex data types:\n", + "\n", + "- **Extruded Blocks with Line Overlay**: Loads Vancouver building blocks data and applies both line and fill-extrusion styles. Each block’s height is based on an attribute, which provides a 3D effect. This is useful for visualizing value distribution across an urban landscape." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "98", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-123.13, 49.254], zoom=11, style=\"dark-matter\", pitch=45, bearing=0\n", + ")\n", + "url = \"https://raw.githubusercontent.com/visgl/deck.gl-data/master/examples/geojson/vancouver-blocks.json\"\n", + "paint_line = {\n", + " \"line-color\": \"white\",\n", + " \"line-width\": 2,\n", + "}\n", + "paint_fill = {\n", + " \"fill-extrusion-color\": {\n", + " \"property\": \"valuePerSqm\",\n", + " \"stops\": [\n", + " [0, \"grey\"],\n", + " [1000, \"yellow\"],\n", + " [5000, \"orange\"],\n", + " [10000, \"darkred\"],\n", + " [50000, \"lightblue\"],\n", + " ],\n", + " },\n", + " \"fill-extrusion-height\": [\"*\", 10, [\"sqrt\", [\"get\", \"valuePerSqm\"]]],\n", + " \"fill-extrusion-opacity\": 0.9,\n", + "}\n", + "m.add_geojson(url, layer_type=\"line\", paint=paint_line, name=\"blocks-line\")\n", + "m.add_geojson(url, layer_type=\"fill-extrusion\", paint=paint_fill, name=\"blocks-fill\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "99", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "100", + "metadata": {}, + "source": [ + "### Marker Cluster\n", + "\n", + "Clusters help manage large datasets, such as earthquake data, by grouping nearby markers. The color and size of clusters change based on the point count, and filters differentiate individual points from clusters. This example uses nested GeoJSON layers for visualizing clustered earthquake occurrences, with separate styles for individual points and clusters." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "101", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-103.59179, 40.66995], zoom=3, style=\"streets\")\n", + "data = \"https://docs.mapbox.com/mapbox-gl-js/assets/earthquakes.geojson\"\n", + "source_args = {\n", + " \"cluster\": True,\n", + " \"cluster_radius\": 50,\n", + " \"cluster_min_points\": 2,\n", + " \"cluster_max_zoom\": 14,\n", + " \"cluster_properties\": {\n", + " \"maxMag\": [\"max\", [\"get\", \"mag\"]],\n", + " \"minMag\": [\"min\", [\"get\", \"mag\"]],\n", + " },\n", + "}\n", + "\n", + "m.add_geojson(\n", + " data,\n", + " layer_type=\"circle\",\n", + " name=\"earthquake-circles\",\n", + " filter=[\"!\", [\"has\", \"point_count\"]],\n", + " paint={\"circle-color\": \"darkblue\"},\n", + " source_args=source_args,\n", + ")\n", + "\n", + "m.add_geojson(\n", + " data,\n", + " layer_type=\"circle\",\n", + " name=\"earthquake-clusters\",\n", + " filter=[\"has\", \"point_count\"],\n", + " paint={\n", + " \"circle-color\": [\n", + " \"step\",\n", + " [\"get\", \"point_count\"],\n", + " \"#51bbd6\",\n", + " 100,\n", + " \"#f1f075\",\n", + " 750,\n", + " \"#f28cb1\",\n", + " ],\n", + " \"circle-radius\": [\"step\", [\"get\", \"point_count\"], 20, 100, 30, 750, 40],\n", + " },\n", + " source_args=source_args,\n", + ")\n", + "\n", + "m.add_geojson(\n", + " data,\n", + " layer_type=\"symbol\",\n", + " name=\"earthquake-labels\",\n", + " filter=[\"has\", \"point_count\"],\n", + " layout={\n", + " \"text-field\": [\"get\", \"point_count_abbreviated\"],\n", + " \"text-size\": 12,\n", + " },\n", + " source_args=source_args,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "102", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/VWvJKwl.png)" + ] + }, + { + "cell_type": "markdown", + "id": "103", + "metadata": {}, + "source": [ + "### Local Vector Data\n", + "\n", + "Local vector files, such as GeoJSON, can be loaded directly into the map. The example downloads a GeoJSON file representing U.S. states and adds it to the map using `open_geojson`." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "104", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://github.com/opengeos/datasets/releases/download/us/us_states.geojson\"\n", + "filepath = \"data/us_states.geojson\"\n", + "leafmap.download_file(url, filepath, quiet=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "105", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "106", + "metadata": {}, + "outputs": [], + "source": [ + "m.open_geojson()" + ] + }, + { + "cell_type": "markdown", + "id": "107", + "metadata": {}, + "source": [ + "### Changing Building Color\n", + "\n", + "You can customize the color and opacity of buildings based on the map’s zoom level. This example changes building colors from orange at lower zoom levels to lighter shades as the zoom level increases. Additionally, the opacity gradually transitions to fully opaque, making buildings more visible at close-up zoom levels." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "108", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-90.73414, 14.55524], zoom=13, style=\"basic\")\n", + "m.set_paint_property(\n", + " \"building\",\n", + " \"fill-color\",\n", + " [\"interpolate\", [\"exponential\", 0.5], [\"zoom\"], 15, \"#e2714b\", 22, \"#eee695\"],\n", + ")\n", + "m.set_paint_property(\n", + " \"building\",\n", + " \"fill-opacity\",\n", + " [\"interpolate\", [\"exponential\", 0.5], [\"zoom\"], 15, 0, 22, 1],\n", + ")\n", + "m.add_layer_control(bg_layers=True)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "109", + "metadata": {}, + "outputs": [], + "source": [ + "m.add_call(\"zoomTo\", 19, {\"duration\": 9000})" + ] + }, + { + "cell_type": "markdown", + "id": "110", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/PayiTON.png)" + ] + }, + { + "cell_type": "markdown", + "id": "111", + "metadata": {}, + "source": [ + "### Adding a New Layer Below Labels\n", + "\n", + "A layer can be added below existing labels on the map to enhance clarity without obscuring labels. The urban areas dataset is displayed as a fill layer in a color and opacity that visually distinguishes it from other map elements. The layer is positioned below symbols, allowing place names to remain visible." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "112", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-88.137343, 35.137451], zoom=4, style=\"streets\")\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": \"https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_urban_areas.geojson\",\n", + "}\n", + "m.add_source(\"urban-areas\", source)\n", + "first_symbol_layer = m.find_first_symbol_layer()\n", + "layer = {\n", + " \"id\": \"urban-areas-fill\",\n", + " \"type\": \"fill\",\n", + " \"source\": \"urban-areas\",\n", + " \"layout\": {},\n", + " \"paint\": {\"fill-color\": \"#f08\", \"fill-opacity\": 0.4},\n", + "}\n", + "m.add_layer(layer, before_id=first_symbol_layer[\"id\"])\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "113", + "metadata": {}, + "source": [ + "### Heat Map\n", + "\n", + "Heatmaps visually represent data density. This example uses earthquake data and configures the heatmap layer with dynamic properties, such as weight, intensity, color, and radius, based on earthquake magnitudes. Circles are added to indicate individual earthquakes, with colors and sizes varying according to their magnitude." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "114", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-120, 50], zoom=2, style=\"basic\")\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": \"https://maplibre.org/maplibre-gl-js/docs/assets/earthquakes.geojson\",\n", + "}\n", + "m.add_source(\"earthquakes\", source)\n", + "layer = {\n", + " \"id\": \"earthquakes-heat\",\n", + " \"type\": \"heatmap\",\n", + " \"source\": \"earthquakes\",\n", + " \"maxzoom\": 9,\n", + " \"paint\": {\n", + " # Increase the heatmap weight based on frequency and property magnitude\n", + " \"heatmap-weight\": [\"interpolate\", [\"linear\"], [\"get\", \"mag\"], 0, 0, 6, 1],\n", + " # Increase the heatmap color weight weight by zoom level\n", + " # heatmap-intensity is a multiplier on top of heatmap-weight\n", + " \"heatmap-intensity\": [\"interpolate\", [\"linear\"], [\"zoom\"], 0, 1, 9, 3],\n", + " # Color ramp for heatmap. Domain is 0 (low) to 1 (high).\n", + " # Begin color ramp at 0-stop with a 0-transparency color\n", + " # to create a blur-like effect.\n", + " \"heatmap-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"heatmap-density\"],\n", + " 0,\n", + " \"rgba(33,102,172,0)\",\n", + " 0.2,\n", + " \"rgb(103,169,207)\",\n", + " 0.4,\n", + " \"rgb(209,229,240)\",\n", + " 0.6,\n", + " \"rgb(253,219,199)\",\n", + " 0.8,\n", + " \"rgb(239,138,98)\",\n", + " 1,\n", + " \"rgb(178,24,43)\",\n", + " ],\n", + " # Adjust the heatmap radius by zoom level\n", + " \"heatmap-radius\": [\"interpolate\", [\"linear\"], [\"zoom\"], 0, 2, 9, 20],\n", + " # Transition from heatmap to circle layer by zoom level\n", + " \"heatmap-opacity\": [\"interpolate\", [\"linear\"], [\"zoom\"], 7, 1, 9, 0],\n", + " },\n", + "}\n", + "m.add_layer(layer, before_id=\"waterway\")\n", + "layer2 = {\n", + " \"id\": \"earthquakes-point\",\n", + " \"type\": \"circle\",\n", + " \"source\": \"earthquakes\",\n", + " \"minzoom\": 7,\n", + " \"paint\": {\n", + " # Size circle radius by earthquake magnitude and zoom level\n", + " \"circle-radius\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"zoom\"],\n", + " 7,\n", + " [\"interpolate\", [\"linear\"], [\"get\", \"mag\"], 1, 1, 6, 4],\n", + " 16,\n", + " [\"interpolate\", [\"linear\"], [\"get\", \"mag\"], 1, 5, 6, 50],\n", + " ],\n", + " # Color circle by earthquake magnitude\n", + " \"circle-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"get\", \"mag\"],\n", + " 1,\n", + " \"rgba(33,102,172,0)\",\n", + " 2,\n", + " \"rgb(103,169,207)\",\n", + " 3,\n", + " \"rgb(209,229,240)\",\n", + " 4,\n", + " \"rgb(253,219,199)\",\n", + " 5,\n", + " \"rgb(239,138,98)\",\n", + " 6,\n", + " \"rgb(178,24,43)\",\n", + " ],\n", + " \"circle-stroke-color\": \"white\",\n", + " \"circle-stroke-width\": 1,\n", + " # Transition from heatmap to circle layer by zoom level\n", + " \"circle-opacity\": [\"interpolate\", [\"linear\"], [\"zoom\"], 7, 0, 8, 1],\n", + " },\n", + "}\n", + "m.add_layer(layer2, before_id=\"waterway\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "115", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/OLCRPKj.png)" + ] + }, + { + "cell_type": "markdown", + "id": "116", + "metadata": {}, + "source": [ + "### Visualizing Population Density\n", + "\n", + "Population density can be calculated and displayed dynamically. This example loads a GeoJSON of Rwandan provinces, calculating density by dividing population by area. The fill color of each province is then adjusted based on density, with different color schemes applied depending on the zoom level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "117", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[30.0222, -1.9596], zoom=7, style=\"streets\")\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": \"https://maplibre.org/maplibre-gl-js/docs/assets/rwanda-provinces.geojson\",\n", + "}\n", + "m.add_source(\"rwanda-provinces\", source)\n", + "layer = {\n", + " \"id\": \"rwanda-provinces\",\n", + " \"type\": \"fill\",\n", + " \"source\": \"rwanda-provinces\",\n", + " \"layout\": {},\n", + " \"paint\": {\n", + " \"fill-color\": [\n", + " \"let\",\n", + " \"density\",\n", + " [\"/\", [\"get\", \"population\"], [\"get\", \"sq-km\"]],\n", + " [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"zoom\"],\n", + " 8,\n", + " [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"var\", \"density\"],\n", + " 274,\n", + " [\"to-color\", \"#edf8e9\"],\n", + " 1551,\n", + " [\"to-color\", \"#006d2c\"],\n", + " ],\n", + " 10,\n", + " [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"var\", \"density\"],\n", + " 274,\n", + " [\"to-color\", \"#eff3ff\"],\n", + " 1551,\n", + " [\"to-color\", \"#08519c\"],\n", + " ],\n", + " ],\n", + " ],\n", + " \"fill-opacity\": 0.7,\n", + " },\n", + "}\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "118", + "metadata": {}, + "source": [ + "## Visualizing Raster Data\n", + "\n", + "### Local Raster Data\n", + "\n", + "To visualize local raster files, use the `add_raster` method. In the example, a Landsat image is downloaded and displayed using two different band combinations:\n", + "\n", + "- **Band Combination 3-2-1 (True Color)**: Simulates natural colors in the RGB channels.\n", + "- **Band Combination 4-3-2**: Enhances vegetation, displaying it in red for better visual contrast.\n", + "These layers are added to the map along with controls to toggle them. You can adjust brightness and contrast with the `vmin` and `vmax` arguments to improve clarity." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "119", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://github.com/opengeos/datasets/releases/download/raster/landsat.tif\"\n", + "filepath = \"landsat.tif\"\n", + "leafmap.download_file(url, filepath, quiet=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "120", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "m.add_raster(filepath, indexes=[3, 2, 1], vmin=0, vmax=100, name=\"Landsat-321\")\n", + "m.add_raster(filepath, indexes=[4, 3, 2], vmin=0, vmax=100, name=\"Landsat-432\")\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "121", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "122", + "metadata": {}, + "source": [ + "A Digital Elevation Model (DEM) is also downloaded and visualized with a terrain color scheme. Leafmap’s `layer_interact` method allows interactive adjustments." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "123", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://github.com/opengeos/datasets/releases/download/raster/srtm90.tif\"\n", + "filepath = \"srtm90.tif\"\n", + "leafmap.download_file(url, filepath, quiet=True)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "124", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"satellite\")\n", + "m.add_raster(filepath, colormap=\"terrain\", name=\"DEM\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "125", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "126", + "metadata": {}, + "source": [ + "### Cloud Optimized GeoTIFF (COG)\n", + "\n", + "Cloud Optimized GeoTIFFs (COG) are large raster files stored on cloud platforms, allowing efficient streaming and loading. This example loads satellite imagery of Libya before and after an event, showing the change over time. Each image is loaded with `add_cog_layer`, and layers can be toggled for comparison. Using `fit_bounds`, the map centers on the COG layer to fit its boundaries." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "127", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"satellite\")\n", + "before = (\n", + " \"https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-07-01.tif\"\n", + ")\n", + "after = (\n", + " \"https://github.com/opengeos/datasets/releases/download/raster/Libya-2023-09-13.tif\"\n", + ")\n", + "m.add_cog_layer(before, name=\"Before\", attribution=\"Maxar\")\n", + "m.add_cog_layer(after, name=\"After\", attribution=\"Maxar\", fit_bounds=True)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "128", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "129", + "metadata": {}, + "source": [ + "### STAC Layer\n", + "\n", + "The SpatioTemporal Asset Catalog (STAC) standard organizes large satellite data collections. With `add_stac_layer`, this example loads Canadian satellite data, displaying both a panchromatic and an RGB layer from the same source. This approach allows easy switching between views." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "130", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"streets\")\n", + "url = \"https://canada-spot-ortho.s3.amazonaws.com/canada_spot_orthoimages/canada_spot5_orthoimages/S5_2007/S5_11055_6057_20070622/S5_11055_6057_20070622.json\"\n", + "m.add_stac_layer(url, bands=[\"pan\"], name=\"Panchromatic\", vmin=0, vmax=150)\n", + "m.add_stac_layer(url, bands=[\"B4\", \"B3\", \"B2\"], name=\"RGB\", vmin=0, vmax=150)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "131", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "132", + "metadata": {}, + "source": [ + "Leafmap also supports loading STAC items from the [Microsoft Planetary Computer](https://planetarycomputer.microsoft.com). The example demonstrates how to load a STAC item from the Planetary Computer and display it on the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "133", + "metadata": {}, + "outputs": [], + "source": [ + "collection = \"landsat-8-c2-l2\"\n", + "item = \"LC08_L2SP_047027_20201204_02_T1\"" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "134", + "metadata": {}, + "outputs": [], + "source": [ + "leafmap.stac_assets(collection=collection, item=item, titiler_endpoint=\"pc\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "135", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"satellite\")\n", + "m.add_stac_layer(\n", + " collection=collection,\n", + " item=item,\n", + " assets=[\"SR_B5\", \"SR_B4\", \"SR_B3\"],\n", + " name=\"Color infrared\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "136", + "metadata": {}, + "source": [ + "## Interacting with the Map\n", + "\n", + "Interactivity allows for a more tailored map experience.\n", + "\n", + "\n", + "### Displaying a Non-Interactive Map\n", + "\n", + "To create a static map, set `interactive=False`. This disables all user interactions, making it ideal for static presentations." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "137", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.65, 45.52], zoom=9, interactive=False, style=\"liberty\", controls={}\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "138", + "metadata": {}, + "source": [ + "### Disabling Scroll Zoom\n", + "\n", + "Use `scroll_zoom=False` to prevent map zooming with the scroll wheel, maintaining a fixed zoom level." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "139", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-122.65, 45.52], zoom=9, scroll_zoom=False, style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "140", + "metadata": {}, + "source": [ + "### Fitting Bounds\n", + "\n", + "The `fit_bounds` method focuses the map on a specified area. In this example, the map centers on Kenya. Additionally, a GeoJSON line is added to the map, and its bounds are automatically calculated with `geojson_bounds` for a custom zoom fit." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "141", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-74.5, 40], zoom=9, style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "142", + "metadata": {}, + "source": [ + "Fit to Kenya." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "143", + "metadata": {}, + "outputs": [], + "source": [ + "bounds = [[32.958984, -5.353521], [43.50585, 5.615985]]\n", + "m.fit_bounds(bounds)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "144", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-77.0214, 38.897], zoom=12, style=\"liberty\")\n", + "\n", + "geojson = {\n", + " \"type\": \"FeatureCollection\",\n", + " \"features\": [\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"geometry\": {\n", + " \"type\": \"LineString\",\n", + " \"properties\": {},\n", + " \"coordinates\": [\n", + " [-77.0366048812866, 38.89873175227713],\n", + " [-77.03364372253417, 38.89876515143842],\n", + " [-77.03364372253417, 38.89549195896866],\n", + " [-77.02982425689697, 38.89549195896866],\n", + " [-77.02400922775269, 38.89387200688839],\n", + " [-77.01519012451172, 38.891416957534204],\n", + " [-77.01521158218382, 38.892068305429156],\n", + " [-77.00813055038452, 38.892051604275686],\n", + " [-77.00832366943358, 38.89143365883688],\n", + " [-77.00818419456482, 38.89082405874451],\n", + " [-77.00815200805664, 38.88989712255097],\n", + " ],\n", + " },\n", + " }\n", + " ],\n", + "}\n", + "\n", + "m.add_source(\"LineString\", {\"type\": \"geojson\", \"data\": geojson})\n", + "layer = {\n", + " \"id\": \"LineString\",\n", + " \"type\": \"line\",\n", + " \"source\": \"LineString\",\n", + " \"layout\": {\"line-join\": \"round\", \"line-cap\": \"round\"},\n", + " \"paint\": {\"line-color\": \"#BF93E4\", \"line-width\": 5},\n", + "}\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "145", + "metadata": {}, + "outputs": [], + "source": [ + "bounds = leafmap.geojson_bounds(geojson)\n", + "bounds" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "146", + "metadata": {}, + "outputs": [], + "source": [ + "m.fit_bounds(bounds)" + ] + }, + { + "cell_type": "markdown", + "id": "147", + "metadata": {}, + "source": [ + "### Restricting Map Panning to an Area\n", + "\n", + "To limit map panning to a specific area, define a bounding box. The map is then restricted within the bounds, ensuring users do not accidentally pan away from the area of interest." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "148", + "metadata": {}, + "outputs": [], + "source": [ + "bounds = [\n", + " [-74.04728500751165, 40.68392799015035],\n", + " [-73.91058699000139, 40.87764500765852],\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "149", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-73.9978, 40.7209], zoom=13, max_bounds=bounds, style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "150", + "metadata": {}, + "source": [ + "### Flying To\n", + "\n", + "The `fly_to` method smoothly navigates to specified coordinates. Parameters like speed and zoom provide control over the fly-to effect. This example flies from the initial map location to New York City." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "151", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-2.242467, 53.478122], zoom=9, style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "152", + "metadata": {}, + "outputs": [], + "source": [ + "m.fly_to(lon=-73.983609, lat=40.754368, zoom=12)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "153", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-74.5, 40], zoom=9, style=\"liberty\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "154", + "metadata": {}, + "outputs": [], + "source": [ + "options = {\n", + " \"lon\": 74.5,\n", + " \"lat\": 40,\n", + " \"zoom\": 9,\n", + " \"bearing\": 0,\n", + " \"speed\": 0.2,\n", + " \"curve\": 1,\n", + " \"essential\": True,\n", + "}\n", + "\n", + "m.fly_to(**options)" + ] + }, + { + "cell_type": "markdown", + "id": "155", + "metadata": {}, + "source": [ + "### Jumping to a Series of Locations\n", + "\n", + "Using `jump_to`, you can navigate between multiple locations. This example sets up a list of coordinates representing cities and automatically pans to each one in sequence. Adding a delay between transitions enhances the animation effect." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "156", + "metadata": {}, + "outputs": [], + "source": [ + "import time" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "157", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[100.507, 13.745], zoom=9, style=\"streets\")\n", + "\n", + "cities = {\n", + " \"type\": \"FeatureCollection\",\n", + " \"features\": [\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [100.507, 13.745]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [98.993, 18.793]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [99.838, 19.924]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [102.812, 17.408]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [100.458, 7.001]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [100.905, 12.935]},\n", + " },\n", + " ],\n", + "}\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "158", + "metadata": {}, + "outputs": [], + "source": [ + "for index, city in enumerate(cities[\"features\"]):\n", + " time.sleep(2)\n", + " coords = city[\"geometry\"][\"coordinates\"]\n", + " m.jump_to({\"center\": coords})" + ] + }, + { + "cell_type": "markdown", + "id": "159", + "metadata": {}, + "source": [ + "### Getting Coordinates of the Mouse Pointer\n", + "\n", + "With widgets, you can display the current mouse pointer coordinates as it moves across the map. This is useful for precision mapping tasks where knowing exact coordinates is essential." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "160", + "metadata": {}, + "outputs": [], + "source": [ + "import ipywidgets as widgets" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "161", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-74.5, 40], zoom=9, style=\"streets\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "162", + "metadata": {}, + "outputs": [], + "source": [ + "m.clicked" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "163", + "metadata": {}, + "outputs": [], + "source": [ + "output = widgets.Output()\n", + "\n", + "\n", + "def log_lng_lat(lng_lat):\n", + " with output:\n", + " output.clear_output()\n", + " print(lng_lat.new)\n", + "\n", + "\n", + "m.observe(log_lng_lat, names=\"clicked\")\n", + "output" + ] + }, + { + "cell_type": "markdown", + "id": "164", + "metadata": {}, + "source": [ + "## Customizing Layer Styles\n", + "\n", + "Customizing layer styles enables personalized map designs.\n", + "\n", + "### Changing Layer Color\n", + "\n", + "Use `style_layer_interact` to interactively change the color of map layers, such as water bodies. This method provides an interactive palette for immediate style changes." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "165", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[12.338, 45.4385], zoom=17, style=\"streets\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "166", + "metadata": {}, + "outputs": [], + "source": [ + "m.style_layer_interact(id=\"water\")" + ] + }, + { + "cell_type": "markdown", + "id": "167", + "metadata": {}, + "source": [ + "### Changing Case of Labels\n", + "\n", + "This example displays labels in upper or lower case based on their properties. The layout options let you customize font, size, and alignment. For example, facility names are displayed in uppercase, and comments are displayed in lowercase for contrast." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "168", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-116.231, 43.604], zoom=11, style=\"streets\")\n", + "geojson = {\n", + " \"type\": \"geojson\",\n", + " \"data\": \"https://maplibre.org/maplibre-gl-js/docs/assets/boise.geojson\",\n", + "}\n", + "m.add_source(\"off-leash-areas\", geojson)\n", + "layer = {\n", + " \"id\": \"off-leash-areas\",\n", + " \"type\": \"symbol\",\n", + " \"source\": \"off-leash-areas\",\n", + " \"layout\": {\n", + " \"icon-image\": \"dog-park-11\",\n", + " \"text-field\": [\n", + " \"format\",\n", + " [\"upcase\", [\"get\", \"FacilityName\"]],\n", + " {\"font-scale\": 0.8},\n", + " \"\\n\",\n", + " {},\n", + " [\"downcase\", [\"get\", \"Comments\"]],\n", + " {\"font-scale\": 0.6},\n", + " ],\n", + " \"text-font\": [\"Open Sans Semibold\", \"Arial Unicode MS Bold\"],\n", + " \"text-offset\": [0, 0.6],\n", + " \"text-anchor\": \"top\",\n", + " },\n", + "}\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "169", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/FzGOovv.png)" + ] + }, + { + "cell_type": "markdown", + "id": "170", + "metadata": {}, + "source": [ + "### Variable Label Placement\n", + "\n", + "Labels can be automatically positioned around features with `text-variable-anchor`. This example defines multiple anchor points for labels to avoid overlap and ensure clear visibility. The radial offset and auto-justification properties create a professional, clutter-free appearance around points of interest." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "171", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-77.04, 38.907], zoom=11, style=\"streets\")\n", + "\n", + "places = {\n", + " \"type\": \"FeatureCollection\",\n", + " \"features\": [\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"Ford's Theater\", \"icon\": \"theatre\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.038659, 38.931567]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"The Gaslight\", \"icon\": \"theatre\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.003168, 38.894651]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"Horrible Harry's\", \"icon\": \"bar\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.090372, 38.881189]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"Bike Party\", \"icon\": \"bicycle\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.052477, 38.943951]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"Rockabilly Rockstars\", \"icon\": \"music\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.031706, 38.914581]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"District Drum Tribe\", \"icon\": \"music\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.020945, 38.878241]},\n", + " },\n", + " {\n", + " \"type\": \"Feature\",\n", + " \"properties\": {\"description\": \"Motown Memories\", \"icon\": \"music\"},\n", + " \"geometry\": {\"type\": \"Point\", \"coordinates\": [-77.007481, 38.876516]},\n", + " },\n", + " ],\n", + "}\n", + "source = {\"type\": \"geojson\", \"data\": places}\n", + "m.add_source(\"places\", source)\n", + "\n", + "layer = {\n", + " \"id\": \"poi-labels\",\n", + " \"type\": \"symbol\",\n", + " \"source\": \"places\",\n", + " \"layout\": {\n", + " \"text-field\": [\"get\", \"description\"],\n", + " \"text-variable-anchor\": [\"top\", \"bottom\", \"left\", \"right\"],\n", + " \"text-radial-offset\": 0.5,\n", + " \"text-justify\": \"auto\",\n", + " \"icon-image\": [\"concat\", [\"get\", \"icon\"], \"_15\"],\n", + " },\n", + "}\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "172", + "metadata": {}, + "outputs": [], + "source": [ + "m.rotate_to(bearing=180, options={\"duration\": 10000})" + ] + }, + { + "cell_type": "markdown", + "id": "173", + "metadata": {}, + "source": [ + "## Adding Custom Components\n", + "\n", + "Enhance your maps by adding custom components such as images, videos, text, color bars, and legends.\n", + "\n", + "### Adding Image\n", + "\n", + "You can add an image as an overlay or as an icon for a specific layer. For instance:\n", + "- Overlaying an image directly on the map at the \"bottom-right\" corner.\n", + "- Adding an icon image to a feature. In the example, a \"cat\" image is loaded, and a marker is added at coordinates `[0, 0]` with a label \"I love kitty!\" above the icon." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "174", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[0.349419, -1.80921], zoom=3, style=\"streets\")\n", + "image = \"https://upload.wikimedia.org/wikipedia/commons/7/7c/201408_cat.png\"\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": {\n", + " \"type\": \"FeatureCollection\",\n", + " \"features\": [\n", + " {\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}\n", + " ],\n", + " },\n", + "}\n", + "\n", + "layer = {\n", + " \"id\": \"points\",\n", + " \"type\": \"symbol\",\n", + " \"source\": \"point\",\n", + " \"layout\": {\n", + " \"icon-image\": \"cat\",\n", + " \"icon-size\": 0.25,\n", + " \"text-field\": \"I love kitty!\",\n", + " \"text-font\": [\"Open Sans Regular\"],\n", + " \"text-offset\": [0, 3],\n", + " \"text-anchor\": \"top\",\n", + " },\n", + "}\n", + "m.add_image(\"cat\", image)\n", + "m.add_source(\"point\", source)\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "175", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/Nq1uV9d.png)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "176", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "image = \"https://i.imgur.com/LmTETPX.png\"\n", + "m.add_image(image=image, position=\"bottom-right\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "177", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "content = ''\n", + "m.add_html(content, bg_color=\"transparent\", position=\"bottom-right\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "178", + "metadata": {}, + "source": [ + "To customize icons, you can also generate icon data with `numpy`, creating unique color gradients and using it as a map icon." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "179", + "metadata": {}, + "outputs": [], + "source": [ + "import numpy as np\n", + "\n", + "\n", + "# Generate the icon data\n", + "width = 64 # The image will be 64 pixels square\n", + "height = 64\n", + "bytes_per_pixel = 4 # Each pixel is represented by 4 bytes: red, green, blue, and alpha\n", + "data = np.zeros((width, width, bytes_per_pixel), dtype=np.uint8)\n", + "\n", + "for x in range(width):\n", + " for y in range(width):\n", + " data[y, x, 0] = int((y / width) * 255) # red\n", + " data[y, x, 1] = int((x / width) * 255) # green\n", + " data[y, x, 2] = 128 # blue\n", + " data[y, x, 3] = 255 # alpha\n", + "\n", + "# Flatten the data array\n", + "flat_data = data.flatten()\n", + "\n", + "# Create the image dictionary\n", + "image_dict = {\n", + " \"width\": width,\n", + " \"height\": height,\n", + " \"data\": flat_data.tolist(),\n", + "}\n", + "\n", + "m = leafmap.Map(center=[0, 0], zoom=1, style=\"liberty\")\n", + "m.add_image(\"gradient\", image_dict)\n", + "source = {\n", + " \"type\": \"geojson\",\n", + " \"data\": {\n", + " \"type\": \"FeatureCollection\",\n", + " \"features\": [\n", + " {\"type\": \"Feature\", \"geometry\": {\"type\": \"Point\", \"coordinates\": [0, 0]}}\n", + " ],\n", + " },\n", + "}\n", + "\n", + "layer = {\n", + " \"id\": \"points\",\n", + " \"type\": \"symbol\",\n", + " \"source\": \"point\",\n", + " \"layout\": {\"icon-image\": \"gradient\"},\n", + "}\n", + "\n", + "m.add_source(\"point\", source)\n", + "m.add_layer(layer)\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "180", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/qWWlnAm.png)" + ] + }, + { + "cell_type": "markdown", + "id": "181", + "metadata": {}, + "source": [ + "### Adding Text\n", + "\n", + "Add text annotations to the map, specifying parameters like font size and background color. For example:\n", + "- Text \"Hello World\" in the bottom-right corner with a transparent background.\n", + "- \"Awesome Text!\" in the top-left corner with a slightly opaque white background, making it stand out." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "182", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"liberty\")\n", + "text = \"Hello World\"\n", + "m.add_text(text, fontsize=20, position=\"bottom-right\")\n", + "text2 = \"Awesome Text!\"\n", + "m.add_text(text2, fontsize=25, bg_color=\"rgba(255, 255, 255, 0.8)\", position=\"top-left\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "183", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/UAtlh3r.png)" + ] + }, + { + "cell_type": "markdown", + "id": "184", + "metadata": {}, + "source": [ + "### Adding GIF\n", + "\n", + "GIFs can be added as animated overlays to bring your map to life. Example: add a sloth GIF in the bottom-right and a second GIF in the bottom-left corner, with a text label indicating “I love sloth!” for added character." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "185", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "image = \"https://i.imgur.com/KeiAsTv.gif\"\n", + "m.add_image(image=image, width=250, height=250, position=\"bottom-right\")\n", + "text = \"I love sloth!🦥\"\n", + "m.add_text(text, fontsize=35, padding=\"20px\")\n", + "image2 = \"https://i.imgur.com/kZC2tpr.gif\"\n", + "m.add_image(image=image2, bg_color=\"transparent\", position=\"bottom-left\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "186", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/auytBtD.png)" + ] + }, + { + "cell_type": "markdown", + "id": "187", + "metadata": {}, + "source": [ + "### Adding HTML\n", + "\n", + "Embed custom HTML content to display various HTML elements, such as emojis or stylized text. You can also adjust the font size and background transparency for better integration into the map design." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "188", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "html = \"\"\"\n", + "\n", + "\n", + "
\n", + "\n", + "🚀\n", + "I will display 🚁
\n", + "I will display 🚂
\n", + "\n", + "\n", + "\n", + "\"\"\"\n", + "m.add_html(html, bg_color=\"transparent\")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "189", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/TgalNOv.png)" + ] + }, + { + "cell_type": "markdown", + "id": "190", + "metadata": {}, + "source": [ + "### Adding Color bar\n", + "\n", + "Adding a color bar enhances data interpretation. In the example:\n", + "1. A Digital Elevation Model (DEM) is displayed with a color ramp from 0 to 1500 meters.\n", + "2. `add_colorbar` method is used to create a color bar with labels, adjusting its position, opacity, and orientation for optimal readability." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "191", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"topo\")\n", + "dem = \"https://github.com/opengeos/datasets/releases/download/raster/dem.tif\"\n", + "m.add_cog_layer(\n", + " dem,\n", + " name=\"DEM\",\n", + " colormap_name=\"terrain\",\n", + " rescale=\"0, 1500\",\n", + " fit_bounds=True,\n", + " nodata=np.nan,\n", + ")\n", + "m.add_colorbar(\n", + " cmap=\"terrain\", vmin=0, vmax=1500, label=\"Elevation (m)\", position=\"bottom-right\"\n", + ")\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "192", + "metadata": {}, + "source": [ + "Make the color bar background transparent to blend seamlessly with the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "193", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"topo\")\n", + "m.add_cog_layer(\n", + " dem,\n", + " name=\"DEM\",\n", + " colormap_name=\"terrain\",\n", + " rescale=\"0, 1500\",\n", + " nodata=np.nan,\n", + " fit_bounds=True,\n", + ")\n", + "m.add_colorbar(\n", + " cmap=\"terrain\",\n", + " vmin=0,\n", + " vmax=1500,\n", + " label=\"Elevation (m)\",\n", + " position=\"bottom-right\",\n", + " transparent=True,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "194", + "metadata": {}, + "source": [ + "Make the color bar vertical for a different layout." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "195", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(style=\"topo\")\n", + "m.add_cog_layer(\n", + " dem,\n", + " name=\"DEM\",\n", + " colormap_name=\"terrain\",\n", + " rescale=\"0, 1500\",\n", + " nodata=np.nan,\n", + " fit_bounds=True,\n", + ")\n", + "m.add_colorbar(\n", + " cmap=\"terrain\",\n", + " vmin=0,\n", + " vmax=1500,\n", + " label=\"Elevation (m)\",\n", + " position=\"bottom-right\",\n", + " width=0.2,\n", + " height=3,\n", + " orientation=\"vertical\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "196", + "metadata": {}, + "source": [ + "### Adding Legend\n", + "\n", + "Custom legends help users understand data classifications. Two methods are shown:\n", + "1. Using built-in legends, such as for NLCD (National Land Cover Database) or wetland types.\n", + "2. Custom legends are built with a dictionary of land cover types and colors. This legend provides descriptive color-coding for various land cover types, with configurable background opacity to blend with the map." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "197", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms\"\n", + "layers = \"NLCD_2021_Land_Cover_L48\"\n", + "m.add_wms_layer(url, layers=layers, name=\"NLCD 2021\")\n", + "m.add_legend(\n", + " title=\"NLCD Land Cover Type\",\n", + " builtin_legend=\"NLCD\",\n", + " bg_color=\"rgba(255, 255, 255, 0.5)\",\n", + " position=\"bottom-left\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "198", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://fwspublicservices.wim.usgs.gov/wetlandsmapservice/services/Wetlands/MapServer/WMSServer\"\n", + "m.add_wms_layer(url, layers=\"1\", name=\"NWI\", opacity=0.6)\n", + "m.add_layer_control()\n", + "m.add_legend(builtin_legend=\"NWI\", title=\"Wetland Type\")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "199", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-100, 40], zoom=3, style=\"positron\")\n", + "m.add_basemap(\"Esri.WorldImagery\")\n", + "url = \"https://www.mrlc.gov/geoserver/mrlc_display/NLCD_2021_Land_Cover_L48/wms\"\n", + "layers = \"NLCD_2021_Land_Cover_L48\"\n", + "m.add_wms_layer(url, layers=layers, name=\"NLCD 2021\")\n", + "\n", + "legend_dict = {\n", + " \"11 Open Water\": \"466b9f\",\n", + " \"12 Perennial Ice/Snow\": \"d1def8\",\n", + " \"21 Developed, Open Space\": \"dec5c5\",\n", + " \"22 Developed, Low Intensity\": \"d99282\",\n", + " \"23 Developed, Medium Intensity\": \"eb0000\",\n", + " \"24 Developed High Intensity\": \"ab0000\",\n", + " \"31 Barren Land (Rock/Sand/Clay)\": \"b3ac9f\",\n", + " \"41 Deciduous Forest\": \"68ab5f\",\n", + " \"42 Evergreen Forest\": \"1c5f2c\",\n", + " \"43 Mixed Forest\": \"b5c58f\",\n", + " \"51 Dwarf Scrub\": \"af963c\",\n", + " \"52 Shrub/Scrub\": \"ccb879\",\n", + " \"71 Grassland/Herbaceous\": \"dfdfc2\",\n", + " \"72 Sedge/Herbaceous\": \"d1d182\",\n", + " \"73 Lichens\": \"a3cc51\",\n", + " \"74 Moss\": \"82ba9e\",\n", + " \"81 Pasture/Hay\": \"dcd939\",\n", + " \"82 Cultivated Crops\": \"ab6c28\",\n", + " \"90 Woody Wetlands\": \"b8d9eb\",\n", + " \"95 Emergent Herbaceous Wetlands\": \"6c9fb8\",\n", + "}\n", + "m.add_legend(\n", + " title=\"NLCD Land Cover Type\",\n", + " legend_dict=legend_dict,\n", + " bg_color=\"rgba(255, 255, 255, 0.5)\",\n", + " position=\"bottom-left\",\n", + ")\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "200", + "metadata": {}, + "source": [ + "![](https://i.imgur.com/dy60trf.png)" + ] + }, + { + "cell_type": "markdown", + "id": "201", + "metadata": {}, + "source": [ + "### Adding Video\n", + "\n", + "Videos can be added with geographic context by specifying corner coordinates. Videos must be listed in multiple formats to ensure compatibility across browsers. The coordinates array should define the video’s location on the map in the order: top-left, top-right, bottom-right, and bottom-left. This is demonstrated by adding drone footage to a satellite map view, enhancing the user experience with real-world visuals." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "202", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-122.514426, 37.562984], zoom=17, bearing=-96, style=\"satellite\"\n", + ")\n", + "urls = [\n", + " \"https://static-assets.mapbox.com/mapbox-gl-js/drone.mp4\",\n", + " \"https://static-assets.mapbox.com/mapbox-gl-js/drone.webm\",\n", + "]\n", + "coordinates = [\n", + " [-122.51596391201019, 37.56238816766053],\n", + " [-122.51467645168304, 37.56410183312965],\n", + " [-122.51309394836426, 37.563391708549425],\n", + " [-122.51423120498657, 37.56161849366671],\n", + "]\n", + "m.add_video(urls, coordinates)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "203", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[-115, 25], zoom=4, style=\"satellite\")\n", + "urls = [\n", + " \"https://data.opengeos.org/patricia_nasa.mp4\",\n", + " \"https://data.opengeos.org/patricia_nasa.webm\",\n", + "]\n", + "coordinates = [\n", + " [-130, 32],\n", + " [-100, 32],\n", + " [-100, 13],\n", + " [-130, 13],\n", + "]\n", + "m.add_video(urls, coordinates)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "markdown", + "id": "204", + "metadata": {}, + "source": [ + "## PMTiles\n", + "\n", + "Leafmap supports visualizing [PMTiles](https://protomaps.com/docs/pmtiles/), which enables efficient storage and fast rendering of vector tiles directly in the browser.\n", + "\n", + "### Protomaps Sample Data\n", + "\n", + "Load Protomaps data in PMTiles format for fast, high-resolution vector map data rendering. Use `pmtiles_metadata()` to fetch details like layer names and map bounds, then style and add these tiles to your map. For instance, the example shows two layers: `buildings` styled in \"steelblue\" and `roads` styled in \"black\"." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "205", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://open.gishub.org/data/pmtiles/protomaps_firenze.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "206", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "\n", + "style = {\n", + " \"version\": 8,\n", + " \"sources\": {\n", + " \"example_source\": {\n", + " \"type\": \"vector\",\n", + " \"url\": \"pmtiles://\" + url,\n", + " \"attribution\": \"PMTiles\",\n", + " }\n", + " },\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"buildings\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"landuse\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\"fill-color\": \"steelblue\"},\n", + " },\n", + " {\n", + " \"id\": \"roads\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"roads\",\n", + " \"type\": \"line\",\n", + " \"paint\": {\"line-color\": \"black\"},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", + "m.add_pmtiles(\n", + " url,\n", + " style=style,\n", + " visible=True,\n", + " opacity=1.0,\n", + " tooltip=True,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "207", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "208", + "metadata": {}, + "source": [ + "### Building Footprint Data\n", + "\n", + "Visualize the [Google-Microsoft Open Buildings dataset](https://beta.source.coop/repositories/vida/google-microsoft-open-buildings/description), managed by VIDA, in PMTiles format. Fetch metadata to identify available layers, apply custom styles to the building footprints, and render them with semi-transparent colors for a clear visualization." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "209", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.source.coop/vida/google-microsoft-open-buildings/pmtiles/go_ms_building_footprints.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "210", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(center=[0, 20], zoom=2)\n", + "m.add_basemap(\"Google Hybrid\", visible=False)\n", + "\n", + "style = {\n", + " \"version\": 8,\n", + " \"sources\": {\n", + " \"example_source\": {\n", + " \"type\": \"vector\",\n", + " \"url\": \"pmtiles://\" + url,\n", + " \"attribution\": \"PMTiles\",\n", + " }\n", + " },\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"buildings\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"building_footprints\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\"fill-color\": \"#3388ff\", \"fill-opacity\": 0.5},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "# style = leafmap.pmtiles_style(url) # Use default style\n", + "\n", + "m.add_pmtiles(\n", + " url,\n", + " style=style,\n", + " visible=True,\n", + " opacity=1.0,\n", + " tooltip=True,\n", + ")\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "211", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "212", + "metadata": {}, + "source": [ + "### Fields of The World\n", + "\n", + "Visualize the Agricultural Field Boundary dataset - Fields of The World ([FTW](https://fieldsofthe.world)). The dataset is available on Source Cooperative at https://source.coop/repositories/kerner-lab/fields-of-the-world/description." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "213", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.source.coop/kerner-lab/fields-of-the-world/ftw-sources.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "214", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "# Define colors for each last digit (0-9)\n", + "style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"Field Polygon\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\n", + " \"fill-color\": [\n", + " \"case\",\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 0],\n", + " \"#FF5733\", # Color for last digit 0\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 1],\n", + " \"#33FF57\", # Color for last digit 1\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 2],\n", + " \"#3357FF\", # Color for last digit 2\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 3],\n", + " \"#FF33A1\", # Color for last digit 3\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 4],\n", + " \"#FF8C33\", # Color for last digit 4\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 5],\n", + " \"#33FFF6\", # Color for last digit 5\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 6],\n", + " \"#A833FF\", # Color for last digit 6\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 7],\n", + " \"#FF333D\", # Color for last digit 7\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 8],\n", + " \"#33FFBD\", # Color for last digit 8\n", + " [\"==\", [\"%\", [\"to-number\", [\"get\", \"id\"]], 10], 9],\n", + " \"#FF9933\", # Color for last digit 9\n", + " \"#FF0000\", # Fallback color if no match\n", + " ],\n", + " \"fill-opacity\": 0.5,\n", + " },\n", + " },\n", + " {\n", + " \"id\": \"Field Outline\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"line\",\n", + " \"paint\": {\"line-color\": \"#ffffff\", \"line-width\": 1, \"line-opacity\": 1},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "m.add_basemap(\"Satellite\")\n", + "m.add_pmtiles(url, style=style, name=\"FTW\", zoom_to_layer=False)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "215", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "216", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map()\n", + "style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"Field Polygon\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\n", + " \"fill-color\": \"#ffff00\",\n", + " \"fill-opacity\": 0.2,\n", + " },\n", + " },\n", + " {\n", + " \"id\": \"Field Outline\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"ftw-sources\",\n", + " \"type\": \"line\",\n", + " \"paint\": {\"line-color\": \"#ff0000\", \"line-width\": 1, \"line-opacity\": 1},\n", + " },\n", + " ],\n", + "}\n", + "\n", + "m.add_basemap(\"Satellite\")\n", + "m.add_pmtiles(url, style=style, name=\"FTW\", zoom_to_layer=False)\n", + "m.add_layer_control()\n", + "m" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "217", + "metadata": {}, + "outputs": [], + "source": [ + "m.layer_interact()" + ] + }, + { + "cell_type": "markdown", + "id": "218", + "metadata": {}, + "source": [ + "### 3D PMTiles\n", + "\n", + "Render global building data in 3D for a realistic, textured experience. Set building colors and extrusion heights to create visually compelling cityscapes. For example, apply color gradients and height scaling based on building attributes to differentiate buildings by their heights." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "219", + "metadata": {}, + "outputs": [], + "source": [ + "url = \"https://data.source.coop/cholmes/overture/overture-buildings.pmtiles\"\n", + "metadata = leafmap.pmtiles_metadata(url)\n", + "print(f\"layer names: {metadata['layer_names']}\")\n", + "print(f\"bounds: {metadata['bounds']}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "220", + "metadata": {}, + "outputs": [], + "source": [ + "m = leafmap.Map(\n", + " center=[-74.0095, 40.7046], zoom=16, pitch=60, bearing=-17, style=\"positron\"\n", + ")\n", + "m.add_basemap(\"OpenStreetMap.Mapnik\")\n", + "m.add_basemap(\"Esri.WorldImagery\", visible=False)\n", + "\n", + "style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"buildings\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"buildings\",\n", + " \"type\": \"fill-extrusion\",\n", + " \"filter\": [\n", + " \">\",\n", + " [\"get\", \"height\"],\n", + " 0,\n", + " ], # only show buildings with height info\n", + " \"paint\": {\n", + " \"fill-extrusion-color\": [\n", + " \"interpolate\",\n", + " [\"linear\"],\n", + " [\"get\", \"height\"],\n", + " 0,\n", + " \"lightgray\",\n", + " 200,\n", + " \"royalblue\",\n", + " 400,\n", + " \"lightblue\",\n", + " ],\n", + " \"fill-extrusion-height\": [\"*\", [\"get\", \"height\"], 1],\n", + " },\n", + " },\n", + " ],\n", + "}\n", + "\n", + "m.add_pmtiles(\n", + " url,\n", + " style=style,\n", + " visible=True,\n", + " opacity=1.0,\n", + " tooltip=True,\n", + " template=\"Height: {{height}}