From 92586cd15866651da55e9918bc37c605fdb72a7b Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 20 Sep 2023 12:48:18 -0400 Subject: [PATCH 01/10] Add support for PMTiles --- ipyleaflet/leaflet.py | 24 +++++++++++++++ js/package.json | 5 +++- js/src/jupyter-leaflet.js | 1 + js/src/layers/PMTilesLayer.js | 55 +++++++++++++++++++++++++++++++++++ js/src/leaflet.js | 3 ++ 5 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 js/src/layers/PMTilesLayer.js diff --git a/ipyleaflet/leaflet.py b/ipyleaflet/leaflet.py index 8e981f7f0..9d2a2272d 100644 --- a/ipyleaflet/leaflet.py +++ b/ipyleaflet/leaflet.py @@ -982,6 +982,30 @@ 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 vector tile service. + attribution: string, default "" + Vector tile service 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) + + class VectorLayer(Layer): """VectorLayer abstract class.""" diff --git a/js/package.json b/js/package.json index 5654238e7..90b9bf6fe 100644 --- a/js/package.json +++ b/js/package.json @@ -47,7 +47,10 @@ "proj4": "^2.6.0", "proj4leaflet": "^1.0.1", "spin.js": "^4.1.0", - "stream-browserify": "^3.0.0" + "stream-browserify": "^3.0.0", + "pmtiles": "^2.5.0", + "maplibre-lib": "^2.2.1", + "maplibre-leaflet": "^0.0.17" }, "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..34029e96c --- /dev/null +++ b/js/src/layers/PMTilesLayer.js @@ -0,0 +1,55 @@ +// Copyright (c) Jupyter Development Team. +// Distributed under the terms of the Modified BSD License. + +const layer = require('./Layer.js'); + +export class LeafletPMTilesLayerModel extends layer.LeafletLayerModel { + defaults() { + return { + ...super.defaults(), + _view_name: 'LeafletPMTilesLayerView', + _model_name: 'LeafletPMTilesLayerModel', + url: '', + style: {}, + }; + } +} + +export class LeafletPMTilesLayerView extends layer.LeafletLayerView { + render() { + this.create_obj(); + this.listenTo(this.model, 'change:url', this.url_changed.bind(this)); + this.listenTo(this.model, 'change:style', this.style_changed.bind(this)); + } + + create_obj() { + var protocol = new pmtiles.Protocol(); + maplibregl.addProtocol("pmtiles", protocol.tile); + + var mapStyle = { + ...this.model.get('style'), + sources: { + ...this.model.get('style').sources, + "pmtiles_source": { + "type": "vector", + "url": "pmtiles://" + this.model.get('url') + } + } + }; + + this.obj = L.maplibreGL({ + style: mapStyle + }); + } + + url_changed() { + var newUrl = "pmtiles://" + this.model.get('url'); + var currentStyle = this.obj.getStyle(); + currentStyle.sources["pmtiles_source"].url = newUrl; + this.obj.setStyle(currentStyle); + } + + style_changed() { + this.obj.setStyle(this.model.get('style')); + } +} diff --git a/js/src/leaflet.js b/js/src/leaflet.js index a4f6a2fa0..1d7dc8cde 100644 --- a/js/src/leaflet.js +++ b/js/src/leaflet.js @@ -16,6 +16,9 @@ require('leaflet-fullscreen'); require('leaflet-transform'); require('leaflet.awesome-markers'); require('leaflet-search'); +require('pmtiles'); +require('maplibre-lib'); +require('maplibre-leaflet'); // Monkey patch GridLayer for smoother URL updates L.patchGridLayer = function (layer) { From a34d3bff440dee5666c4ba1f5c87836be0a5dd78 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 20 Sep 2023 13:08:45 -0400 Subject: [PATCH 02/10] Fix yarnpkg error --- js/src/leaflet.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/src/leaflet.js b/js/src/leaflet.js index 1d7dc8cde..3d7f474c0 100644 --- a/js/src/leaflet.js +++ b/js/src/leaflet.js @@ -17,8 +17,8 @@ require('leaflet-transform'); require('leaflet.awesome-markers'); require('leaflet-search'); require('pmtiles'); -require('maplibre-lib'); -require('maplibre-leaflet'); +require('maplibre-gl'); +require('maplibre-gl-leaflet'); // Monkey patch GridLayer for smoother URL updates L.patchGridLayer = function (layer) { From 6261995c2280da941dcce8d2d5bc89207000045e Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 20 Sep 2023 13:11:12 -0400 Subject: [PATCH 03/10] Fix package.json --- js/package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/js/package.json b/js/package.json index 90b9bf6fe..dcded69f6 100644 --- a/js/package.json +++ b/js/package.json @@ -49,8 +49,8 @@ "spin.js": "^4.1.0", "stream-browserify": "^3.0.0", "pmtiles": "^2.5.0", - "maplibre-lib": "^2.2.1", - "maplibre-leaflet": "^0.0.17" + "maplibre-gl": "^2.2.1", + "maplibre-gl-leaflet": "^0.0.17" }, "devDependencies": { "@jupyterlab/builder": "^3.6.0", From d1370ca9d1e4d965093b8eb550b057c42eede2c4 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Wed, 20 Sep 2023 13:24:46 -0400 Subject: [PATCH 04/10] Remove maplibre-gl-leaflet --- js/package.json | 5 ++--- js/src/leaflet.js | 1 - 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/js/package.json b/js/package.json index dcded69f6..2ae13eb91 100644 --- a/js/package.json +++ b/js/package.json @@ -48,9 +48,8 @@ "proj4leaflet": "^1.0.1", "spin.js": "^4.1.0", "stream-browserify": "^3.0.0", - "pmtiles": "^2.5.0", - "maplibre-gl": "^2.2.1", - "maplibre-gl-leaflet": "^0.0.17" + "pmtiles": "^2.7.1", + "maplibre-gl": "^2.4.0" }, "devDependencies": { "@jupyterlab/builder": "^3.6.0", diff --git a/js/src/leaflet.js b/js/src/leaflet.js index 3d7f474c0..ef37046e6 100644 --- a/js/src/leaflet.js +++ b/js/src/leaflet.js @@ -18,7 +18,6 @@ require('leaflet.awesome-markers'); require('leaflet-search'); require('pmtiles'); require('maplibre-gl'); -require('maplibre-gl-leaflet'); // Monkey patch GridLayer for smoother URL updates L.patchGridLayer = function (layer) { From 974af2b5fa9e9f454f3bd10919d38eb192f7d6d0 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 23 Sep 2023 16:10:11 -0400 Subject: [PATCH 05/10] Update package --- ipyleaflet/leaflet.py | 4 ++-- js/package.json | 3 +-- js/src/layers/PMTilesLayer.js | 34 ++++++++++++---------------------- js/src/leaflet.js | 3 +-- 4 files changed, 16 insertions(+), 28 deletions(-) diff --git a/ipyleaflet/leaflet.py b/ipyleaflet/leaflet.py index 9d2a2272d..61213731c 100644 --- a/ipyleaflet/leaflet.py +++ b/ipyleaflet/leaflet.py @@ -991,9 +991,9 @@ class PMTilesLayer(Layer): Attributes ---------- url: string, default "" - Url to the vector tile service. + Url to the PMTiles archive. attribution: string, default "" - Vector tile service attribution. + PMTiles archive attribution. style: dict, default {} CSS Styles to apply to the vector data. """ diff --git a/js/package.json b/js/package.json index 2ae13eb91..1bf339061 100644 --- a/js/package.json +++ b/js/package.json @@ -48,8 +48,7 @@ "proj4leaflet": "^1.0.1", "spin.js": "^4.1.0", "stream-browserify": "^3.0.0", - "pmtiles": "^2.7.1", - "maplibre-gl": "^2.4.0" + "protomaps-leaflet": "^1.24.0" }, "devDependencies": { "@jupyterlab/builder": "^3.6.0", diff --git a/js/src/layers/PMTilesLayer.js b/js/src/layers/PMTilesLayer.js index 34029e96c..50891dc75 100644 --- a/js/src/layers/PMTilesLayer.js +++ b/js/src/layers/PMTilesLayer.js @@ -23,31 +23,21 @@ export class LeafletPMTilesLayerView extends layer.LeafletLayerView { } create_obj() { - var protocol = new pmtiles.Protocol(); - maplibregl.addProtocol("pmtiles", protocol.tile); + this.obj = protomapsL.leafletLayer({url:this.model.get('url')}) - var mapStyle = { - ...this.model.get('style'), - sources: { - ...this.model.get('style').sources, - "pmtiles_source": { - "type": "vector", - "url": "pmtiles://" + this.model.get('url') - } - } - }; - - this.obj = L.maplibreGL({ - style: mapStyle - }); } - url_changed() { - var newUrl = "pmtiles://" + this.model.get('url'); - var currentStyle = this.obj.getStyle(); - currentStyle.sources["pmtiles_source"].url = newUrl; - this.obj.setStyle(currentStyle); - } + model_events() { + super.model_events(); + this.listenTo( + this.model, + 'change:url', + function () { + this.obj.setUrl(this.model.get('url')); + }, + this + ); + } style_changed() { this.obj.setStyle(this.model.get('style')); diff --git a/js/src/leaflet.js b/js/src/leaflet.js index ef37046e6..ac7ad8d14 100644 --- a/js/src/leaflet.js +++ b/js/src/leaflet.js @@ -16,8 +16,7 @@ require('leaflet-fullscreen'); require('leaflet-transform'); require('leaflet.awesome-markers'); require('leaflet-search'); -require('pmtiles'); -require('maplibre-gl'); +require('protomaps-leaflet'); // Monkey patch GridLayer for smoother URL updates L.patchGridLayer = function (layer) { From 93048f49de9276bc5e942fd0ab93680e75c4e8c1 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 23 Sep 2023 16:18:16 -0400 Subject: [PATCH 06/10] Fix linter issue --- js/src/layers/PMTilesLayer.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/js/src/layers/PMTilesLayer.js b/js/src/layers/PMTilesLayer.js index 50891dc75..df22f0a46 100644 --- a/js/src/layers/PMTilesLayer.js +++ b/js/src/layers/PMTilesLayer.js @@ -23,8 +23,7 @@ export class LeafletPMTilesLayerView extends layer.LeafletLayerView { } create_obj() { - this.obj = protomapsL.leafletLayer({url:this.model.get('url')}) - + this.obj = L.protomapsL.leafletLayer({url:this.model.get('url')}) } model_events() { From 2070e94b3aed9dd16b645c06f3facab6e1b45a89 Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 23 Sep 2023 16:29:38 -0400 Subject: [PATCH 07/10] Fix linter issue --- js/src/layers/PMTilesLayer.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/js/src/layers/PMTilesLayer.js b/js/src/layers/PMTilesLayer.js index df22f0a46..f787b6de7 100644 --- a/js/src/layers/PMTilesLayer.js +++ b/js/src/layers/PMTilesLayer.js @@ -23,7 +23,9 @@ export class LeafletPMTilesLayerView extends layer.LeafletLayerView { } create_obj() { - this.obj = L.protomapsL.leafletLayer({url:this.model.get('url')}) + this.obj = L.protomapsL.leafletLayer({ + url:this.model.get('url') + }) } model_events() { From ba3cd8da4a123db7a87befd0a17e534e7dfda92a Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 23 Sep 2023 18:00:26 -0400 Subject: [PATCH 08/10] Change layer view --- ipyleaflet/leaflet.py | 4 ++++ js/src/layers/PMTilesLayer.js | 27 ++++++++++++--------------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/ipyleaflet/leaflet.py b/ipyleaflet/leaflet.py index 61213731c..2a5f8d5c7 100644 --- a/ipyleaflet/leaflet.py +++ b/ipyleaflet/leaflet.py @@ -1005,6 +1005,10 @@ class PMTilesLayer(Layer): 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/src/layers/PMTilesLayer.js b/js/src/layers/PMTilesLayer.js index f787b6de7..3a73a29fe 100644 --- a/js/src/layers/PMTilesLayer.js +++ b/js/src/layers/PMTilesLayer.js @@ -10,25 +10,19 @@ export class LeafletPMTilesLayerModel extends layer.LeafletLayerModel { _view_name: 'LeafletPMTilesLayerView', _model_name: 'LeafletPMTilesLayerModel', url: '', + attribution: '', style: {}, }; } } export class LeafletPMTilesLayerView extends layer.LeafletLayerView { - render() { - this.create_obj(); - this.listenTo(this.model, 'change:url', this.url_changed.bind(this)); - this.listenTo(this.model, 'change:style', this.style_changed.bind(this)); - } - create_obj() { - this.obj = L.protomapsL.leafletLayer({ - url:this.model.get('url') - }) - } - - model_events() { + this.obj = L.protomapsL.leafletLayer(this.model.get('url'), this.get_options()); + // this.model.on('msg:custom', this.handle_message.bind(this)); + } + + model_events() { super.model_events(); this.listenTo( this.model, @@ -40,7 +34,10 @@ export class LeafletPMTilesLayerView extends layer.LeafletLayerView { ); } - style_changed() { - this.obj.setStyle(this.model.get('style')); + handle_message(content) { + if (content.msg == 'add_inspector') { + this.obj.addInspector(this.map_view.obj); + } + } } -} + \ No newline at end of file From abf98059c15e9479afba44b41eef4701c14143ec Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sat, 14 Oct 2023 22:33:21 -0400 Subject: [PATCH 09/10] Incorporate jtmiclat code --- ipyleaflet/leaflet.py | 1 + js/src/layers/PMTilesLayer.js | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/ipyleaflet/leaflet.py b/ipyleaflet/leaflet.py index 2a5f8d5c7..e5a430411 100644 --- a/ipyleaflet/leaflet.py +++ b/ipyleaflet/leaflet.py @@ -1010,6 +1010,7 @@ def add_inspector(self): """ self.send({'msg': 'add_inspector'}) + class VectorLayer(Layer): """VectorLayer abstract class.""" diff --git a/js/src/layers/PMTilesLayer.js b/js/src/layers/PMTilesLayer.js index 3a73a29fe..45696fff5 100644 --- a/js/src/layers/PMTilesLayer.js +++ b/js/src/layers/PMTilesLayer.js @@ -2,6 +2,7 @@ // 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() { @@ -18,8 +19,12 @@ export class LeafletPMTilesLayerModel extends layer.LeafletLayerModel { export class LeafletPMTilesLayerView extends layer.LeafletLayerView { create_obj() { - this.obj = L.protomapsL.leafletLayer(this.model.get('url'), this.get_options()); - // this.model.on('msg:custom', this.handle_message.bind(this)); + var options = { + ...this.get_options(), + url: this.model.get('url'), + ...protomapsL.json_style(this.model.get('style')), + }; + this.obj = protomapsL.leafletLayer(options); } model_events() { From b0aa5d7d6b8e35e472fc3bd86cb348681bc6dcef Mon Sep 17 00:00:00 2001 From: Qiusheng Wu Date: Sun, 15 Oct 2023 00:09:02 -0400 Subject: [PATCH 10/10] Add PMTiles notebook --- examples/PMTiles.ipynb | 94 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 examples/PMTiles.ipynb 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 +}