diff --git a/examples/PMTiles.ipynb b/examples/PMTiles.ipynb new file mode 100644 index 000000000..f0f858bac --- /dev/null +++ b/examples/PMTiles.ipynb @@ -0,0 +1,94 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Set up for JupyterLite\n", + "try:\n", + " import piplite\n", + " await piplite.install('ipyleaflet')\n", + "except ImportError:\n", + " pass" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from ipyleaflet import Map, basemaps, PMTilesLayer" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "m = Map(center=[52.963529, 4.776306], zoom=7, basemap=basemaps.CartoDB.DarkMatter, scroll_wheel_zoom=True)\n", + "m.layout.height = '600px'\n", + "\n", + "vl = PMTilesLayer(url=\"https://storage.googleapis.com/ahp-research/overture/pmtiles/overture.pmtiles\", \n", + " style = {\n", + " \"layers\": [\n", + " {\n", + " \"id\": \"admins\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"admins\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\"fill-color\": \"#BDD3C7\", \"fill-opacity\": 0.1},\n", + " },\n", + " {\n", + " \"id\": \"buildings\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"buildings\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\"fill-color\": \"#FFFFB3\", \"fill-opacity\": 0.5},\n", + " },\n", + " {\n", + " \"id\": \"places\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"places\",\n", + " \"type\": \"fill\",\n", + " \"paint\": {\"fill-color\": \"#BEBADA\", \"fill-opacity\": 0.5},\n", + " },\n", + " {\n", + " \"id\": \"roads\",\n", + " \"source\": \"example_source\",\n", + " \"source-layer\": \"roads\",\n", + " \"type\": \"line\",\n", + " \"paint\": {\"line-color\": \"#FB8072\"},\n", + " },\n", + " ],\n", + " })\n", + "m.add(vl)\n", + "m" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.5" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/ipyleaflet/leaflet.py b/ipyleaflet/leaflet.py index 8e981f7f0..e5a430411 100644 --- a/ipyleaflet/leaflet.py +++ b/ipyleaflet/leaflet.py @@ -982,6 +982,35 @@ def redraw(self): self.send({'msg': 'redraw'}) +class PMTilesLayer(Layer): + """PMTilesLayer class, with Layer as parent class. + + PMTiles layer. + + + Attributes + ---------- + url: string, default "" + Url to the PMTiles archive. + attribution: string, default "" + PMTiles archive attribution. + style: dict, default {} + CSS Styles to apply to the vector data. + """ + + _view_name = Unicode('LeafletPMTilesLayerView').tag(sync=True) + _model_name = Unicode('LeafletPMTilesLayerModel').tag(sync=True) + + url = Unicode().tag(sync=True, o=True) + attribution = Unicode().tag(sync=True, o=True) + style = Dict().tag(sync=True, o=True) + + def add_inspector(self): + """Add an inspector to the layer. + """ + self.send({'msg': 'add_inspector'}) + + class VectorLayer(Layer): """VectorLayer abstract class.""" diff --git a/js/package.json b/js/package.json index 5654238e7..1bf339061 100644 --- a/js/package.json +++ b/js/package.json @@ -47,7 +47,8 @@ "proj4": "^2.6.0", "proj4leaflet": "^1.0.1", "spin.js": "^4.1.0", - "stream-browserify": "^3.0.0" + "stream-browserify": "^3.0.0", + "protomaps-leaflet": "^1.24.0" }, "devDependencies": { "@jupyterlab/builder": "^3.6.0", diff --git a/js/src/jupyter-leaflet.js b/js/src/jupyter-leaflet.js index 508cfed25..794feac36 100644 --- a/js/src/jupyter-leaflet.js +++ b/js/src/jupyter-leaflet.js @@ -9,6 +9,7 @@ export * from './layers/AwesomeIcon.js'; export * from './layers/Popup.js'; export * from './layers/RasterLayer.js'; export * from './layers/TileLayer.js'; +export * from './layers/PMTilesLayer.js'; export * from './layers/VectorTileLayer.js'; export * from './layers/LocalTileLayer.js'; export * from './layers/WMSLayer.js'; diff --git a/js/src/layers/PMTilesLayer.js b/js/src/layers/PMTilesLayer.js new file mode 100644 index 000000000..45696fff5 --- /dev/null +++ b/js/src/layers/PMTilesLayer.js @@ -0,0 +1,48 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +const layer = require('./Layer.js'); +const protomapsL = require('protomaps-leaflet'); + +export class LeafletPMTilesLayerModel extends layer.LeafletLayerModel { + defaults() { + return { + ...super.defaults(), + _view_name: 'LeafletPMTilesLayerView', + _model_name: 'LeafletPMTilesLayerModel', + url: '', + attribution: '', + style: {}, + }; + } +} + +export class LeafletPMTilesLayerView extends layer.LeafletLayerView { + create_obj() { + var options = { + ...this.get_options(), + url: this.model.get('url'), + ...protomapsL.json_style(this.model.get('style')), + }; + this.obj = protomapsL.leafletLayer(options); + } + + model_events() { + super.model_events(); + this.listenTo( + this.model, + 'change:url', + function () { + this.obj.setUrl(this.model.get('url')); + }, + this + ); + } + + handle_message(content) { + if (content.msg == 'add_inspector') { + this.obj.addInspector(this.map_view.obj); + } + } + } + \ No newline at end of file diff --git a/js/src/leaflet.js b/js/src/leaflet.js index a4f6a2fa0..ac7ad8d14 100644 --- a/js/src/leaflet.js +++ b/js/src/leaflet.js @@ -16,6 +16,7 @@ require('leaflet-fullscreen'); require('leaflet-transform'); require('leaflet.awesome-markers'); require('leaflet-search'); +require('protomaps-leaflet'); // Monkey patch GridLayer for smoother URL updates L.patchGridLayer = function (layer) {