From 7450f14ce13ed48d1467fe3bed8f66ad052aaed3 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Wed, 4 Apr 2018 07:06:55 -0700 Subject: [PATCH 1/5] Replace leaflet with mapbox-gl --- packages/geojson-extension/package.json | 5 +- packages/geojson-extension/src/index.tsx | 95 ++++++++---------------- yarn.lock | 42 ++++++++++- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/packages/geojson-extension/package.json b/packages/geojson-extension/package.json index 047f182e5..13399d30e 100644 --- a/packages/geojson-extension/package.json +++ b/packages/geojson-extension/package.json @@ -24,10 +24,11 @@ "@jupyterlab/rendermime-interfaces": "^1.0.3", "@phosphor/messaging": "^1.2.2", "@phosphor/widgets": "^1.5.0", - "leaflet": "^1.2.0" + "leaflet": "^1.2.0", + "mapbox-gl": "^0.44.2" }, "devDependencies": { - "@types/leaflet": "^1.2.0", + "@types/mapbox-gl": "^0.44.1", "rimraf": "~2.6.2", "typescript": "~2.6.2" }, diff --git a/packages/geojson-extension/src/index.tsx b/packages/geojson-extension/src/index.tsx index 918c318ad..bf2c961d5 100644 --- a/packages/geojson-extension/src/index.tsx +++ b/packages/geojson-extension/src/index.tsx @@ -13,15 +13,12 @@ import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; -import * as leaflet from 'leaflet'; +import * as mapboxgl from 'mapbox-gl'; import 'leaflet/dist/leaflet.css'; import '../style/index.css'; -import * as iconRetinaUrl from 'leaflet/dist/images/marker-icon-2x.png'; -import * as iconUrl from 'leaflet/dist/images/marker-icon.png'; -import * as shadowUrl from 'leaflet/dist/images/marker-shadow.png'; /** * The CSS class to add to the GeoJSON Widget. @@ -39,39 +36,7 @@ const CSS_ICON_CLASS = 'jp-MaterialIcon jp-GeoJSONIcon'; export const MIME_TYPE = 'application/geo+json'; -/** - * Set base path for leaflet images. - */ - -// https://github.com/Leaflet/Leaflet/issues/4968 -// Marker file names are hard-coded in the leaflet source causing -// issues with webpack. -// This workaround allows webpack to inline all marker URLs. - -delete (leaflet.Icon.Default.prototype as any)['_getIconUrl']; - -leaflet.Icon.Default.mergeOptions({ - iconRetinaUrl: iconRetinaUrl, - iconUrl: iconUrl, - shadowUrl: shadowUrl -}); - - -/** - * The url template that leaflet tile layers. - * See http://leafletjs.com/reference-1.0.3.html#tilelayer - */ -const URL_TEMPLATE: string = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png'; - -/** - * The options for leaflet tile layers. - * See http://leafletjs.com/reference-1.0.3.html#tilelayer - */ -const LAYER_OPTIONS: leaflet.TileLayerOptions = { - attribution: 'Map data (c) OpenStreetMap contributors', - minZoom: 0, - maxZoom: 18 -}; +mapboxgl.accessToken = 'pk.eyJ1IjoibWlja3QiLCJhIjoiLXJIRS1NbyJ9.EfVT76g4A5dyuApW_zuIFQ'; export @@ -82,14 +47,7 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { constructor(options: IRenderMime.IRendererOptions) { super(); this.addClass(CSS_CLASS); - this._mimeType = options.mimeType; - // Create leaflet map object - // trackResize option set to false as it is not needed to track - // window.resize events since we have individual phosphor resize - // events. - this._map = leaflet.map(this.node, { - trackResize: false - }); + this._mimeType = options.mimeType; } /** @@ -107,16 +65,20 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { */ renderModel(model: IRenderMime.IMimeModel): Promise { const data = model.data[this._mimeType] as any | GeoJSON.GeoJsonObject; - const metadata = model.metadata[this._mimeType] as any || {}; + // const metadata = model.metadata[this._mimeType] as any || {}; return new Promise((resolve, reject) => { - // Add leaflet tile layer to map - leaflet.tileLayer( - metadata.url_template || URL_TEMPLATE, - metadata.layer_options || LAYER_OPTIONS - ).addTo(this._map); - // Create GeoJSON layer from data and add to map - this._geoJSONLayer = leaflet.geoJSON(data).addTo(this._map); - this.update(); + // Add GeoJSON layer to map + if (this._map) { + this._map.addLayer({ + id: 'layer', + type: 'symbol', + source: { + type: 'geojson', + data + } + }); + this.update(); + } resolve(); }); } @@ -125,16 +87,22 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { * A message handler invoked on an `'after-attach'` message. */ protected onAfterAttach(msg: Message): void { + this._map = new mapboxgl.Map({ + container: this.node, + style: 'mapbox://styles/mapbox/light-v9', + minZoom: 0, + maxZoom: 18 + }); if (this.parent.hasClass('jp-OutputArea-child')) { // Disable scroll zoom by default to avoid conflicts with notebook scroll - this._map.scrollWheelZoom.disable(); + this._map.scrollZoom.disable(); // Enable scroll zoom on map focus - this._map.on('blur', (event) => { - this._map.scrollWheelZoom.disable(); + this._map.on('blur', (event: Event) => { + this._map.scrollZoom.disable(); }); // Disable scroll zoom on blur - this._map.on('focus', (event) => { - this._map.scrollWheelZoom.enable(); + this._map.on('focus', (event: Event) => { + this._map.scrollZoom.enable(); }); } this.update(); @@ -159,13 +127,12 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { */ protected onUpdateRequest(msg: Message): void { // Update map size after update - if (this.isVisible) this._map.invalidateSize(); - // Update map size after panel/window is resized - this._map.fitBounds(this._geoJSONLayer.getBounds()); + if (this._map && this.isVisible) { + this._map.resize(); + } } - private _map: leaflet.Map; - private _geoJSONLayer: leaflet.GeoJSON; + private _map: mapboxgl.Map; private _mimeType: string; } diff --git a/yarn.lock b/yarn.lock index 9d29cb421..ebf4cc90a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -503,9 +503,9 @@ version "0.5.0" resolved "https://registry.npmjs.org/@types/katex/-/katex-0.5.0.tgz#a7dbb981f1bc89c8b1b5e7fa8a1ce2617ab2f358" -"@types/leaflet@^1.2.0": - version "1.2.5" - resolved "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.2.5.tgz#b172502c298849cadd034314dabfc936beff7636" +"@types/mapbox-gl@^0.44.1": + version "0.44.1" + resolved "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-0.44.1.tgz#6d8618ab2a28e8b78ce2cca403f28a227c9f217d" dependencies: "@types/geojson" "*" @@ -3439,6 +3439,42 @@ mapbox-gl@^0.44.0: vt-pbf "^3.0.1" webworkify "^1.5.0" +mapbox-gl@^0.44.2: + version "0.44.2" + resolved "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-0.44.2.tgz#8c118ba8c5c15b054272644f30877309db0f8ee2" + dependencies: + "@mapbox/gl-matrix" "^0.0.1" + "@mapbox/mapbox-gl-supported" "^1.3.0" + "@mapbox/point-geometry" "^0.1.0" + "@mapbox/shelf-pack" "^3.1.0" + "@mapbox/tiny-sdf" "^1.1.0" + "@mapbox/unitbezier" "^0.0.0" + "@mapbox/vector-tile" "^1.3.0" + "@mapbox/whoots-js" "^3.0.0" + brfs "^1.4.0" + bubleify "^0.7.0" + csscolorparser "~1.0.2" + earcut "^2.1.3" + geojson-rewind "^0.3.0" + geojson-vt "^3.0.0" + gray-matter "^3.0.8" + grid-index "^1.0.0" + jsonlint-lines-primitives "~1.6.0" + minimist "0.0.8" + package-json-versionify "^1.0.2" + pbf "^3.0.5" + quickselect "^1.0.0" + rw "^1.3.3" + shuffle-seed "^1.1.6" + sort-object "^0.3.2" + supercluster "^2.3.0" + through2 "^2.0.3" + tinyqueue "^1.1.0" + unassertify "^2.0.0" + unflowify "^1.0.0" + vt-pbf "^3.0.1" + webworkify "^1.5.0" + marching-simplex-table@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/marching-simplex-table/-/marching-simplex-table-1.0.0.tgz#bc16256e0f8f9b558aa9b2872f8832d9433f52ea" From 7024c720f7344ef5a4dbf5c32c1b4af96a985875 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Wed, 4 Apr 2018 08:12:06 -0700 Subject: [PATCH 2/5] Fix Typescript error --- packages/geojson-extension/src/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/geojson-extension/src/index.tsx b/packages/geojson-extension/src/index.tsx index bf2c961d5..70de3c880 100644 --- a/packages/geojson-extension/src/index.tsx +++ b/packages/geojson-extension/src/index.tsx @@ -13,7 +13,7 @@ import { IRenderMime } from '@jupyterlab/rendermime-interfaces'; -import * as mapboxgl from 'mapbox-gl'; +import mapboxgl = require('mapbox-gl'); import 'leaflet/dist/leaflet.css'; From 987d92b5a9934b1ed591fd6fb89e4a00131f3c6d Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Thu, 5 Apr 2018 12:06:31 -0700 Subject: [PATCH 3/5] Update mapbox-gl implementation --- packages/geojson-extension/src/index.tsx | 56 ++++++++++++++---------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/packages/geojson-extension/src/index.tsx b/packages/geojson-extension/src/index.tsx index 70de3c880..63f30cf22 100644 --- a/packages/geojson-extension/src/index.tsx +++ b/packages/geojson-extension/src/index.tsx @@ -69,14 +69,8 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { return new Promise((resolve, reject) => { // Add GeoJSON layer to map if (this._map) { - this._map.addLayer({ - id: 'layer', - type: 'symbol', - source: { - type: 'geojson', - data - } - }); + const dataSource = this._map.getSource('geojson') as mapboxgl.GeoJSONSource; + dataSource.setData(data); this.update(); } resolve(); @@ -89,22 +83,38 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { protected onAfterAttach(msg: Message): void { this._map = new mapboxgl.Map({ container: this.node, - style: 'mapbox://styles/mapbox/light-v9', - minZoom: 0, - maxZoom: 18 + style: 'mapbox://styles/mapbox/light-v9?optimize=true' }); - if (this.parent.hasClass('jp-OutputArea-child')) { - // Disable scroll zoom by default to avoid conflicts with notebook scroll - this._map.scrollZoom.disable(); - // Enable scroll zoom on map focus - this._map.on('blur', (event: Event) => { - this._map.scrollZoom.disable(); - }); - // Disable scroll zoom on blur - this._map.on('focus', (event: Event) => { - this._map.scrollZoom.enable(); - }); - } + this._map.on('style.load', () => { + this._map.addSource('geojson', { + type: 'geojson', + data: { 'type':'FeatureCollection', 'features': [] } + }); + this._map.addLayer({ + id: 'geojson-points', + type: 'circle', + source: 'geojson', + paint: { + 'circle-color': 'red', + 'circle-stroke-color': 'white', + 'circle-stroke-width': { stops: [[0,0.1], [18,3]], base: 1.2 }, + 'circle-radius': { stops: [[15,3], [18,5]], base: 1.2 } + } + }) + }); + // If in a notebook context + // if (this.parent.hasClass('jp-OutputArea-child')) { + // // Disable scroll zoom by default to avoid conflicts with notebook scroll + // this._map.scrollZoom.disable(); + // // Enable scroll zoom on map focus + // this._map.on('blur', (event: Event) => { + // this._map.scrollZoom.disable(); + // }); + // // Disable scroll zoom on blur + // this._map.on('focus', (event: Event) => { + // this._map.scrollZoom.enable(); + // }); + // } this.update(); } From 5895d0918a684576ee02b2bfd4e8e1afc5caa06b Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Fri, 6 Apr 2018 09:25:13 -0700 Subject: [PATCH 4/5] More updates --- packages/geojson-extension/src/index.tsx | 54 ++++++++++++------------ 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/packages/geojson-extension/src/index.tsx b/packages/geojson-extension/src/index.tsx index 63f30cf22..521c36cb2 100644 --- a/packages/geojson-extension/src/index.tsx +++ b/packages/geojson-extension/src/index.tsx @@ -66,14 +66,33 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { renderModel(model: IRenderMime.IMimeModel): Promise { const data = model.data[this._mimeType] as any | GeoJSON.GeoJsonObject; // const metadata = model.metadata[this._mimeType] as any || {}; + this._map = new mapboxgl.Map({ + container: this.node, + style: 'mapbox://styles/mapbox/light-v9?optimize=true', + zoom: 10 + }); return new Promise((resolve, reject) => { // Add GeoJSON layer to map - if (this._map) { - const dataSource = this._map.getSource('geojson') as mapboxgl.GeoJSONSource; - dataSource.setData(data); + this._map.on('style.load', () => { + this._map.addSource('geojson', { + type: 'geojson', + data + }); + this._map.addLayer({ + id: 'geojson-points', + type: 'circle', + source: 'geojson', + paint: { + 'circle-color': 'red', + 'circle-stroke-color': 'white', + 'circle-stroke-width': { stops: [[0,0.1], [18,3]], base: 1.2 }, + 'circle-radius': { stops: [[15,3], [18,5]], base: 1.2 } + } + }); + this.update(); - } - resolve(); + resolve(); + }); }); } @@ -81,27 +100,6 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { * A message handler invoked on an `'after-attach'` message. */ protected onAfterAttach(msg: Message): void { - this._map = new mapboxgl.Map({ - container: this.node, - style: 'mapbox://styles/mapbox/light-v9?optimize=true' - }); - this._map.on('style.load', () => { - this._map.addSource('geojson', { - type: 'geojson', - data: { 'type':'FeatureCollection', 'features': [] } - }); - this._map.addLayer({ - id: 'geojson-points', - type: 'circle', - source: 'geojson', - paint: { - 'circle-color': 'red', - 'circle-stroke-color': 'white', - 'circle-stroke-width': { stops: [[0,0.1], [18,3]], base: 1.2 }, - 'circle-radius': { stops: [[15,3], [18,5]], base: 1.2 } - } - }) - }); // If in a notebook context // if (this.parent.hasClass('jp-OutputArea-child')) { // // Disable scroll zoom by default to avoid conflicts with notebook scroll @@ -115,7 +113,7 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { // this._map.scrollZoom.enable(); // }); // } - this.update(); + // this.update(); } /** @@ -138,11 +136,13 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { protected onUpdateRequest(msg: Message): void { // Update map size after update if (this._map && this.isVisible) { + this._map.fitBounds(this._map.getBounds()); this._map.resize(); } } private _map: mapboxgl.Map; + // private _geoJSONLayer: mapboxgl.GeoJSONSource; private _mimeType: string; } From 3bda4781188f724d5719b52cc2ac207410715340 Mon Sep 17 00:00:00 2001 From: Grant Nestor Date: Fri, 6 Apr 2018 16:19:43 -0700 Subject: [PATCH 5/5] Use turf.js to compute bounding box from geojson --- packages/geojson-extension/package.json | 1 + packages/geojson-extension/src/index.tsx | 8 +++++--- yarn.lock | 17 +++++++++++++++++ 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/packages/geojson-extension/package.json b/packages/geojson-extension/package.json index 13399d30e..46d213e86 100644 --- a/packages/geojson-extension/package.json +++ b/packages/geojson-extension/package.json @@ -24,6 +24,7 @@ "@jupyterlab/rendermime-interfaces": "^1.0.3", "@phosphor/messaging": "^1.2.2", "@phosphor/widgets": "^1.5.0", + "@turf/bbox": "^6.0.1", "leaflet": "^1.2.0", "mapbox-gl": "^0.44.2" }, diff --git a/packages/geojson-extension/src/index.tsx b/packages/geojson-extension/src/index.tsx index 521c36cb2..424344f41 100644 --- a/packages/geojson-extension/src/index.tsx +++ b/packages/geojson-extension/src/index.tsx @@ -15,6 +15,8 @@ import { import mapboxgl = require('mapbox-gl'); +import bbox from '@turf/bbox'; + import 'leaflet/dist/leaflet.css'; import '../style/index.css'; @@ -68,7 +70,7 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { // const metadata = model.metadata[this._mimeType] as any || {}; this._map = new mapboxgl.Map({ container: this.node, - style: 'mapbox://styles/mapbox/light-v9?optimize=true', + style: 'mapbox://styles/mapbox/streets-v9?optimize=true', zoom: 10 }); return new Promise((resolve, reject) => { @@ -78,6 +80,8 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { type: 'geojson', data }); + const [minX, minY, maxX, maxY] = bbox(data); + this._map.fitBounds([[minX, minY], [maxX, maxY]], { maxZoom: 15, padding: 100 }); this._map.addLayer({ id: 'geojson-points', type: 'circle', @@ -89,7 +93,6 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { 'circle-radius': { stops: [[15,3], [18,5]], base: 1.2 } } }); - this.update(); resolve(); }); @@ -136,7 +139,6 @@ class RenderedGeoJSON extends Widget implements IRenderMime.IRenderer { protected onUpdateRequest(msg: Message): void { // Update map size after update if (this._map && this.isVisible) { - this._map.fitBounds(this._map.getBounds()); this._map.resize(); } } diff --git a/yarn.lock b/yarn.lock index ebf4cc90a..d1a559a8b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -311,6 +311,23 @@ d3-collection "1" d3-interpolate "1" +"@turf/bbox@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/@turf/bbox/-/bbox-6.0.1.tgz#b966075771475940ee1c16be2a12cf389e6e923a" + dependencies: + "@turf/helpers" "6.x" + "@turf/meta" "6.x" + +"@turf/helpers@6.x": + version "6.1.3" + resolved "https://registry.npmjs.org/@turf/helpers/-/helpers-6.1.3.tgz#0001a5c4a3bff25b4bbbc64f8713a383c9ab9e94" + +"@turf/meta@6.x": + version "6.0.1" + resolved "https://registry.npmjs.org/@turf/meta/-/meta-6.0.1.tgz#cf6f3f2263a3d24fc8d6a7e90f0420bbc44c090d" + dependencies: + "@turf/helpers" "6.x" + "@types/d3-array@*": version "1.2.1" resolved "https://registry.npmjs.org/@types/d3-array/-/d3-array-1.2.1.tgz#e489605208d46a1c9d980d2e5772fa9c75d9ec65"