From 34f4b6b8dce3b2a28a145e931696b71af40092b3 Mon Sep 17 00:00:00 2001 From: Henri Normak Date: Thu, 19 Nov 2020 09:29:27 +0200 Subject: [PATCH 1/5] chore: update mapbox-gl dependency Needed to add some type overrides to get the tests to pass --- package-lock.json | 29 +++++++++++++++++++---------- package.json | 6 +++--- src/geojson-layer.ts | 25 ++++++++++++++----------- src/layer.ts | 4 +++- src/source.ts | 6 ++---- 5 files changed, 41 insertions(+), 29 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4176040d3..7722f150e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -598,9 +598,9 @@ "dev": true }, "@types/mapbox-gl": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-1.10.2.tgz", - "integrity": "sha512-fgsFVivgopfge1HwUmkMqjEg0XO0sMqI2au3GKhg8g6vpexjszvy63chNpLiz5MOLu4APbKXm18JsKSmhzzoMw==", + "version": "1.12.8", + "resolved": "https://registry.npmjs.org/@types/mapbox-gl/-/mapbox-gl-1.12.8.tgz", + "integrity": "sha512-a/FCVFr/i43dVl9M+PUVeuoG9IH5H1TfXWGcGoeyXgsdgDHmM6dI5u5CabW2GVu97hMQ64gbeR5N3jZ5sqWaMw==", "dev": true, "requires": { "@types/geojson": "*" @@ -3015,9 +3015,9 @@ } }, "ieee754": { - "version": "1.1.13", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", - "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "dev": true }, "import-local": { @@ -4108,9 +4108,9 @@ } }, "mapbox-gl": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.10.1.tgz", - "integrity": "sha512-0aHt+lFUpYfvh0kMIqXqNXqoYMuhuAsMlw87TbhWrw78Tx2zfuPI0Lx31/YPUgJ+Ire0tzQ4JnuBL7acDNXmMg==", + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/mapbox-gl/-/mapbox-gl-1.12.0.tgz", + "integrity": "sha512-B3URR4qY9R/Bx+DKqP8qmGCai8IOZYMSZF7ZSvcCZaYTaOYhQQi8ErTEDZtFMOR0ZPj7HFWOkkhl5SqvDfpJpA==", "dev": true, "requires": { "@mapbox/geojson-rewind": "^0.5.0", @@ -4133,7 +4133,7 @@ "potpack": "^1.0.1", "quickselect": "^2.0.0", "rw": "^1.3.3", - "supercluster": "^7.0.0", + "supercluster": "^7.1.0", "tinyqueue": "^2.0.3", "vt-pbf": "^3.1.1" }, @@ -4143,6 +4143,15 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true + }, + "supercluster": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/supercluster/-/supercluster-7.1.0.tgz", + "integrity": "sha512-LDasImUAFMhTqhK+cUXfy9C2KTUqJ3gucLjmNLNFmKWOnDUBxLFLH9oKuXOTCLveecmxh8fbk8kgh6Q0gsfe2w==", + "dev": true, + "requires": { + "kdbush": "^3.0.0" + } } } }, diff --git a/package.json b/package.json index 01a462fe4..8e6fdd2d1 100644 --- a/package.json +++ b/package.json @@ -72,7 +72,7 @@ "supercluster": "^7.0.0" }, "peerDependencies": { - "mapbox-gl": "^1.10.1", + "mapbox-gl": "^1.12.0", "prop-types": "^15.6.2", "react": "^16.11.0", "react-dom": "^16.11.0" @@ -84,7 +84,7 @@ "@types/enzyme-adapter-react-16": "^1.0.3", "@types/geojson": "7946.0.4", "@types/jest": "24.0.19", - "@types/mapbox-gl": "^1.10.2", + "@types/mapbox-gl": "^1.12.8", "@types/node": "8.0.29", "@types/prettier": "1.10.0", "@types/prop-types": "15.5.6", @@ -95,7 +95,7 @@ "enzyme-adapter-react-16": "1.6.0", "husky": "^0.14.3", "jest": "24.9.0", - "mapbox-gl": "^1.10.1", + "mapbox-gl": "^1.12.0", "prettier": "1.10.2", "prop-types": "15.6.2", "react": "^16.11.0", diff --git a/src/geojson-layer.ts b/src/geojson-layer.ts index b9554acc4..ea09fa0bc 100644 --- a/src/geojson-layer.ts +++ b/src/geojson-layer.ts @@ -148,17 +148,20 @@ export class GeoJSONLayer extends React.Component { visibility }; - map.addLayer( - { - id: layerId, - source: this.id, - type, - paint, - layout, - ...layerOptions - }, - before - ); + const layer: MapboxGL.Layer = { + id: layerId, + source: this.id, + // TODO: Fix mapbox-gl types + // tslint:disable-next-line:no-any + type: type as any, + // TODO: Fix mapbox-gl types + // tslint:disable-next-line:no-any + paint: paint as any, + layout, + ...layerOptions + }; + + map.addLayer(layer, before); this.mapLayerMouseHandlers(type); }; diff --git a/src/layer.ts b/src/layer.ts index 2248ba33d..3f30a41f4 100644 --- a/src/layer.ts +++ b/src/layer.ts @@ -188,7 +188,9 @@ export default class Layer extends React.Component { // tslint:disable-next-line:no-any type: type as any, layout, - paint, + // TODO: Fix mapbox-gl types + // tslint:disable-next-line:no-any + paint: paint as any, metadata }; diff --git a/src/source.ts b/src/source.ts index e95b103b8..c7f64c7c3 100644 --- a/src/source.ts +++ b/src/source.ts @@ -12,9 +12,7 @@ export interface Props { onSourceLoaded?: (source: GeoJSONSource | TilesJson) => void; } -export interface LayerWithBefore extends Layer { - before?: string; -} +export type LayerWithBefore = Layer & { before?: string }; export class Source extends React.Component { private id = this.props.id; @@ -79,7 +77,7 @@ export class Source extends React.Component { let { layers = [] } = map.getStyle(); layers = layers - .map((layer, idx) => { + .map((layer, idx): LayerWithBefore => { const { id: before } = layers[idx + 1] || { id: undefined }; return { ...layer, before }; }) From f771c64ecdbc0e0783bb4ff924b571fdbd6e2505 Mon Sep 17 00:00:00 2001 From: Henri Normak Date: Thu, 19 Nov 2020 09:42:45 +0200 Subject: [PATCH 2/5] feat: avoid replacing entire vector source if url/tiles change --- src/source.ts | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/source.ts b/src/source.ts index c7f64c7c3..20feaf965 100644 --- a/src/source.ts +++ b/src/source.ts @@ -106,13 +106,37 @@ export class Source extends React.Component { public componentDidUpdate(prevProps: Props) { const { geoJsonSource, tileJsonSource, map } = prevProps; + const source = map.getSource(this.id); // Update tilesJsonSource if (tileJsonSource && this.props.tileJsonSource) { + let urlUpdated = false; + let tilesUpdated = false; + + if (source && source.type === 'vector') { + const hasNewSourceUrl = + tileJsonSource.url !== this.props.tileJsonSource.url; + + if (hasNewSourceUrl && this.props.tileJsonSource.url) { + source.setUrl(this.props.tileJsonSource.url); + urlUpdated = true; + } + + const hasNewSourceTiles = + tileJsonSource.tiles !== this.props.tileJsonSource.tiles; + if (hasNewSourceTiles && this.props.tileJsonSource.tiles) { + source.setTiles(this.props.tileJsonSource.tiles); + tilesUpdated = true; + } + } + + // Prefer the more targetted updates, but fallback to swapping out the entire source + // This applies to raster tile sources, for example const hasNewTilesSource = - tileJsonSource.url !== this.props.tileJsonSource.url || + (!urlUpdated && tileJsonSource.url !== this.props.tileJsonSource.url) || // Check for reference equality on tiles array - tileJsonSource.tiles !== this.props.tileJsonSource.tiles || + (!tilesUpdated && + tileJsonSource.tiles !== this.props.tileJsonSource.tiles) || tileJsonSource.minzoom !== this.props.tileJsonSource.minzoom || tileJsonSource.maxzoom !== this.props.tileJsonSource.maxzoom; @@ -130,9 +154,9 @@ export class Source extends React.Component { this.props.geoJsonSource && this.props.geoJsonSource.data !== geoJsonSource.data && this.props.geoJsonSource.data && - map.getSource(this.id) + source && + source.type === 'geojson' ) { - const source = map.getSource(this.id) as GeoJSONSource; source.setData(this.props.geoJsonSource.data); } } From 5a8ea51c888cf672d8bbb9e910fed6ed784043a6 Mon Sep 17 00:00:00 2001 From: Henri Normak Date: Mon, 23 Nov 2020 17:53:09 +0200 Subject: [PATCH 3/5] fix: make checks more explicitly against undefined --- src/source.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/source.ts b/src/source.ts index 20feaf965..5734d1336 100644 --- a/src/source.ts +++ b/src/source.ts @@ -117,14 +117,14 @@ export class Source extends React.Component { const hasNewSourceUrl = tileJsonSource.url !== this.props.tileJsonSource.url; - if (hasNewSourceUrl && this.props.tileJsonSource.url) { + if (hasNewSourceUrl && this.props.tileJsonSource.url !== undefined) { source.setUrl(this.props.tileJsonSource.url); urlUpdated = true; } const hasNewSourceTiles = tileJsonSource.tiles !== this.props.tileJsonSource.tiles; - if (hasNewSourceTiles && this.props.tileJsonSource.tiles) { + if (hasNewSourceTiles && this.props.tileJsonSource.tiles !== undefined) { source.setTiles(this.props.tileJsonSource.tiles); tilesUpdated = true; } From 3a2b1f43907cb9a71dd67463ca87862f79b2909c Mon Sep 17 00:00:00 2001 From: Henri Normak Date: Mon, 23 Nov 2020 17:54:21 +0200 Subject: [PATCH 4/5] refactor: improve readability --- src/source.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/source.ts b/src/source.ts index 5734d1336..a8dbaf4d8 100644 --- a/src/source.ts +++ b/src/source.ts @@ -124,7 +124,11 @@ export class Source extends React.Component { const hasNewSourceTiles = tileJsonSource.tiles !== this.props.tileJsonSource.tiles; - if (hasNewSourceTiles && this.props.tileJsonSource.tiles !== undefined) { + + if ( + hasNewSourceTiles && + this.props.tileJsonSource.tiles !== undefined + ) { source.setTiles(this.props.tileJsonSource.tiles); tilesUpdated = true; } From e0a2f66732e48c2a28c94464ad6805fc5e1e5b5d Mon Sep 17 00:00:00 2001 From: Henri Normak Date: Tue, 24 Nov 2020 09:05:59 +0200 Subject: [PATCH 5/5] feat: add example using vector source --- example/generateRaws.js | 3 +- example/src/demos/raws/vectorLayer.raw | 125 +++++++++++++++++++++++++ example/src/demos/sections.tsx | 9 ++ example/src/demos/vectorLayer.tsx | 125 +++++++++++++++++++++++++ 4 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 example/src/demos/raws/vectorLayer.raw create mode 100644 example/src/demos/vectorLayer.tsx diff --git a/example/generateRaws.js b/example/generateRaws.js index 60c62a717..ba3032bd4 100644 --- a/example/generateRaws.js +++ b/example/generateRaws.js @@ -8,7 +8,8 @@ const files = [ 'htmlCluster.tsx', 'switchStyle.tsx', 'geojsonLayer.tsx', - 'heatmap.tsx' + 'heatmap.tsx', + 'vectorLayer.tsx' ]; files.forEach((file) => { diff --git a/example/src/demos/raws/vectorLayer.raw b/example/src/demos/raws/vectorLayer.raw new file mode 100644 index 000000000..82a4897cc --- /dev/null +++ b/example/src/demos/raws/vectorLayer.raw @@ -0,0 +1,125 @@ +import * as React from 'react'; +import ReactMapboxGl, { Layer, Source } from '../../../'; + +import styled from 'styled-components'; + +// tslint:disable-next-line:no-var-requires +const { token, styles } = require('./config.json'); + +const Map = ReactMapboxGl({ accessToken: token }); + +const Container = styled.div` + position: relative; + height: 100%; + flex: 1; +`; + +const Button = styled.button` + border: 1px solid #3770c6; + background-color: rgb(84, 152, 255); + height: 100%; + color: white; + font-size: 13px; + padding: 6px 12px; + border-radius: 6px; + cursor: pointer; + outline: none; + :hover { + background-color: #3770c6; + } +`; +const Indicator = styled.div` + padding: 6px 10px; + background-color: white; +`; +const BottomBar = styled.div` + position: absolute; + bottom: 20px; + left: 20px; + right: 20px; + height: 40px; + display: flex; + justify-content: space-between; + align-items: center; +`; + +const mapStyle = { + height: '100%', + width: '100%' +}; + +export interface Props { + // tslint:disable-next-line:no-any + onStyleLoad?: (map: any) => any; +} + +const lineLayout = { + 'line-cap': 'round' as 'round', + 'line-join': 'round' as 'round' +}; + +const linePaint = { + 'line-color': '#4790E5', + 'line-width': 2 +}; + +export interface State { + render: 'cloudfront' | 'mapillary'; +} + +export default class VectorLayer extends React.Component { + public state: State = { + render: 'mapillary' + }; + + // tslint:disable-next-line:no-any + private onStyleLoad = (map: any) => { + const { onStyleLoad } = this.props; + return onStyleLoad && onStyleLoad(map); + }; + + public render() { + const { render } = this.state; + const tileUrl = + render === 'mapillary' + ? 'https://tiles3.mapillary.com/v0.1/{z}/{x}/{y}.mvt' + : 'https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt'; + + return ( + + + + + + + + + Using tiles from {render} + + + ); + } +} diff --git a/example/src/demos/sections.tsx b/example/src/demos/sections.tsx index 094cff9d0..c3bb3cc4d 100644 --- a/example/src/demos/sections.tsx +++ b/example/src/demos/sections.tsx @@ -6,6 +6,7 @@ import ThreeDMap from './threeDMap'; import HtmlCluster from './htmlCluster'; import SwitchStyle from './switchStyle'; import GeoJsonLayer from './geojsonLayer'; +import VectorLayer from './vectorLayer'; import Heatmap from './heatmap'; import { Live } from '../live'; import raw from 'raw.macro'; @@ -18,6 +19,7 @@ const HtmlClusterRaw = raw('./raws/htmlCluster.raw'); const SwitchStyleRaw = raw('./raws/switchStyle.raw'); const GeoJsonLayerRaw = raw('./raws/geojsonLayer.raw'); const HeatmapRaw = raw('./raws/heatmap.raw'); +const VectorLayerRaw = raw('./raws/vectorLayer.raw'); export const sections = [ { @@ -82,5 +84,12 @@ export const sections = [ components: ['ReactMapboxGl', 'GeoJsonLayer'], DemoComponent: GeoJsonLayer, reactLive: + }, + { + shortTitle: 'vector-source', + title: 'Display data from vector tile', + components: ['ReactMapboxGl', 'Source', 'Layer'], + DemoComponent: VectorLayer, + reactLive: } ]; diff --git a/example/src/demos/vectorLayer.tsx b/example/src/demos/vectorLayer.tsx new file mode 100644 index 000000000..82a4897cc --- /dev/null +++ b/example/src/demos/vectorLayer.tsx @@ -0,0 +1,125 @@ +import * as React from 'react'; +import ReactMapboxGl, { Layer, Source } from '../../../'; + +import styled from 'styled-components'; + +// tslint:disable-next-line:no-var-requires +const { token, styles } = require('./config.json'); + +const Map = ReactMapboxGl({ accessToken: token }); + +const Container = styled.div` + position: relative; + height: 100%; + flex: 1; +`; + +const Button = styled.button` + border: 1px solid #3770c6; + background-color: rgb(84, 152, 255); + height: 100%; + color: white; + font-size: 13px; + padding: 6px 12px; + border-radius: 6px; + cursor: pointer; + outline: none; + :hover { + background-color: #3770c6; + } +`; +const Indicator = styled.div` + padding: 6px 10px; + background-color: white; +`; +const BottomBar = styled.div` + position: absolute; + bottom: 20px; + left: 20px; + right: 20px; + height: 40px; + display: flex; + justify-content: space-between; + align-items: center; +`; + +const mapStyle = { + height: '100%', + width: '100%' +}; + +export interface Props { + // tslint:disable-next-line:no-any + onStyleLoad?: (map: any) => any; +} + +const lineLayout = { + 'line-cap': 'round' as 'round', + 'line-join': 'round' as 'round' +}; + +const linePaint = { + 'line-color': '#4790E5', + 'line-width': 2 +}; + +export interface State { + render: 'cloudfront' | 'mapillary'; +} + +export default class VectorLayer extends React.Component { + public state: State = { + render: 'mapillary' + }; + + // tslint:disable-next-line:no-any + private onStyleLoad = (map: any) => { + const { onStyleLoad } = this.props; + return onStyleLoad && onStyleLoad(map); + }; + + public render() { + const { render } = this.state; + const tileUrl = + render === 'mapillary' + ? 'https://tiles3.mapillary.com/v0.1/{z}/{x}/{y}.mvt' + : 'https://d25uarhxywzl1j.cloudfront.net/v0.1/{z}/{x}/{y}.mvt'; + + return ( + + + + + + + + + Using tiles from {render} + + + ); + } +}