Skip to content

Commit 8895031

Browse files
authored
feat: Vector Tile Source Support (#90)
* base vector tile source support * undo lint-staged edits * fix: top level export added * fix: handle VectorTileSource in useFeature * fix: useFeature top level hooks, tile source provider is seperate file * fix: vector tile source provider types, typedoc comments * fix: remove unused imports * fix: use imperative syntax for clarity * feat: updated coverage
1 parent ad9ab34 commit 8895031

10 files changed

+186
-31
lines changed

__mocks__/azure-maps-control.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
class DataSource {
2+
add = jest.fn()
3+
clear = jest.fn()
4+
remove = jest.fn()
5+
importDataFromUrl = jest.fn()
6+
setOptions = jest.fn()
7+
}
8+
19
module.exports = {
210
Map: jest.fn(() => ({
311
controls: {
@@ -111,13 +119,11 @@ module.exports = {
111119
}))
112120
},
113121
source: {
114-
DataSource: jest.fn(() => ({
115-
add: jest.fn(),
116-
clear: jest.fn(),
117-
remove: jest.fn(),
118-
importDataFromUrl: jest.fn(),
119-
setOptions: jest.fn()
120-
}))
122+
DataSource,
123+
VectorTileSource: jest.fn((id, options) => ({
124+
getId: jest.fn(() => id),
125+
getOptions: jest.fn(() => options)
126+
}))
121127
},
122128
Shape: jest.fn(() => ({
123129
setCoordinates: jest.fn(),

assets/coverage.png

-91.6 KB
Loading

src/components/AzureMapFeature/useFeature.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useEffect } from 'react'
22
import { DataSourceType, IAzureMapFeature, ShapeType, FeatureType } from '../../types'
33
import { useCheckRef } from '../../hooks/useCheckRef'
4+
import atlas from 'azure-maps-control'
45

56
export const useFeature = (
67
{ setCoords, setProperties }: IAzureMapFeature,
@@ -10,17 +11,25 @@ export const useFeature = (
1011
) => {
1112
// Simple feature's usecases and methods
1213
useCheckRef<DataSourceType, FeatureType>(dataSourceRef, featureRef, (dref, fref) => {
13-
dref.add(fref)
14-
return () => {
15-
dref.remove(fref)
14+
if(dref instanceof atlas.source.DataSource){
15+
dref.add(fref)
16+
return () => {
17+
dref.remove(fref)
18+
}
19+
} else if (dataSourceRef instanceof atlas.source.VectorTileSource) {
20+
console.error(`Unable to add Feature(${fref.id}) to VectorTileSource(${dataSourceRef.getId()}): AzureMapFeature has to be a child of AzureMapDataSourceProvider`)
1621
}
1722
})
1823

1924
// Shape's usecases and methods
2025
useCheckRef<DataSourceType, ShapeType>(dataSourceRef, shapeRef, (dref, sref) => {
21-
dref.add(sref)
22-
return () => {
23-
dref.remove(sref)
26+
if(dref instanceof atlas.source.DataSource){
27+
dref.add(sref)
28+
return () => {
29+
dref.remove(sref)
30+
}
31+
} else if (dataSourceRef instanceof atlas.source.VectorTileSource) {
32+
console.error(`Unable to add Shape(${sref.getId()}) to VectorTileSource(${dataSourceRef.getId()}): AzureMapFeature has to be a child of AzureMapDataSourceProvider`)
2433
}
2534
})
2635

src/contexts/AzureMapDataSourceContext.test.tsx

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useContext } from 'react'
22
import { renderHook } from '@testing-library/react-hooks'
3-
import { Map } from 'azure-maps-control'
3+
import atlas, { Map } from 'azure-maps-control'
44
import React from 'react'
55
import { AzureMapsContext } from '../contexts/AzureMapContext'
66
import {
@@ -65,30 +65,36 @@ describe('AzureMapDataSourceProvider tests', () => {
6565
const { result } = renderHook(() => useContextConsumer(), {
6666
wrapper: wrapWithDataSourceContext({ id: 'id', dataFromUrl: 'dataFromUrl' })
6767
})
68-
expect(result.current.dataSourceRef?.importDataFromUrl).toHaveBeenCalledWith('dataFromUrl')
68+
expect(result.current.dataSourceRef).toBeInstanceOf(atlas.source.DataSource)
69+
expect((result.current.dataSourceRef as atlas.source.DataSource).importDataFromUrl).toHaveBeenCalledWith('dataFromUrl')
6970
})
7071

7172
it('should call add collection if collection was not falsy', () => {
7273
const { result } = renderHook(() => useContextConsumer(), {
7374
wrapper: wrapWithDataSourceContext({ id: 'id', collection: [] })
7475
})
75-
expect(result.current.dataSourceRef?.add).toHaveBeenCalledWith([])
76-
expect(result.current.dataSourceRef?.clear).toHaveBeenCalledWith()
76+
expect(result.current.dataSourceRef).toBeInstanceOf(atlas.source.DataSource)
77+
const dataSourceRef = result.current.dataSourceRef as atlas.source.DataSource
78+
expect(dataSourceRef.add).toHaveBeenCalledWith([])
79+
expect(dataSourceRef.clear).toHaveBeenCalledWith()
7780
})
7881

7982
it('should call add collection and clear method if collection was changed', () => {
8083
const { result, rerender } = renderHook(() => useContextConsumer(), {
8184
wrapper: wrapWithDataSourceContext({ id: 'id', collection: [] })
8285
})
8386
rerender({})
84-
expect(result.current.dataSourceRef?.add).toHaveBeenCalledTimes(2)
85-
expect(result.current.dataSourceRef?.clear).toHaveBeenCalledTimes(1)
87+
expect(result.current.dataSourceRef).toBeInstanceOf(atlas.source.DataSource)
88+
const dataSourceRef = result.current.dataSourceRef as atlas.source.DataSource
89+
expect(dataSourceRef.add).toHaveBeenCalledTimes(2)
90+
expect(dataSourceRef.clear).toHaveBeenCalledTimes(1)
8691
})
8792

8893
it('should call setOptions and clear method if options was changed', () => {
8994
const { result, rerender } = renderHook(() => useContextConsumer(), {
9095
wrapper: wrapWithDataSourceContext({ id: 'id', options: { option: 'option' } })
9196
})
92-
expect(result.current.dataSourceRef?.setOptions).toHaveBeenLastCalledWith({ option: 'option' })
97+
expect(result.current.dataSourceRef).toBeInstanceOf(atlas.source.DataSource)
98+
expect((result.current.dataSourceRef as atlas.source.DataSource).setOptions).toHaveBeenLastCalledWith({ option: 'option' })
9399
})
94100
})

src/contexts/AzureMapDataSourceContext.tsx

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,13 @@ const AzureMapDataSourceStatefulProvider = ({
3838
mref.events.add(eventType as any, dref, events[eventType])
3939
}
4040
mref.sources.add(dref)
41-
if (dataFromUrl) {
42-
dref.importDataFromUrl(dataFromUrl)
43-
}
44-
if (collection) {
45-
dref.add(collection)
41+
if (dref instanceof atlas.source.DataSource) {
42+
if (dataFromUrl) {
43+
dref.importDataFromUrl(dataFromUrl)
44+
}
45+
if (collection) {
46+
dref.add(collection)
47+
}
4648
}
4749
})
4850

@@ -74,5 +76,6 @@ const AzureMapDataSourceStatefulProvider = ({
7476
export {
7577
AzureMapDataSourceContext,
7678
AzureMapDataSourceConsumer,
77-
AzureMapDataSourceStatefulProvider as AzureMapDataSourceProvider
79+
AzureMapDataSourceStatefulProvider as AzureMapDataSourceProvider,
80+
Provider as AzureMapDataSourceRawProvider
7881
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { renderHook } from '@testing-library/react-hooks'
2+
import React, { useContext } from 'react'
3+
import { Map } from 'azure-maps-control'
4+
import { IAzureVectorTileSourceStatefulProviderProps } from "../types"
5+
import { AzureMapsContext } from './AzureMapContext'
6+
import { AzureMapVectorTileSourceProvider } from './AzureMapVectorTileSourceProvider'
7+
import { AzureMapDataSourceContext } from '../contexts/AzureMapDataSourceContext'
8+
9+
const mapContextProps = {
10+
mapRef: null,
11+
isMapReady: false,
12+
setMapReady: jest.fn(),
13+
removeMapRef: jest.fn(),
14+
setMapRef: jest.fn()
15+
}
16+
const mapRef = new Map('fake', {})
17+
18+
const useContextConsumer = () => {
19+
const dataSourceContext = useContext(AzureMapDataSourceContext)
20+
return dataSourceContext
21+
}
22+
23+
const wrapWithVectorTileSourceContext = (props: IAzureVectorTileSourceStatefulProviderProps) => ({
24+
children
25+
}: {
26+
children?: any
27+
}) => {
28+
return (
29+
<AzureMapsContext.Provider
30+
value={{
31+
...mapContextProps,
32+
mapRef
33+
}}
34+
>
35+
<AzureMapVectorTileSourceProvider {...{ ...props }}>{children}</AzureMapVectorTileSourceProvider>
36+
</AzureMapsContext.Provider>
37+
)
38+
}
39+
40+
describe('AzureMapVectorTileSourceProvider tests', () => {
41+
it('should create data source with passed id and options', () => {
42+
const { result } = renderHook(() => useContextConsumer(), {
43+
wrapper: wrapWithVectorTileSourceContext({ id: 'id', options: { minZoom: 0, maxZoom: 12 } })
44+
})
45+
46+
expect(result.current.dataSourceRef?.getId()).toEqual('id')
47+
expect(result.current.dataSourceRef?.getOptions()).toEqual({ minZoom: 0, maxZoom: 12 })
48+
})
49+
50+
it('should add data source to the map ref on mount', () => {
51+
mapRef.sources.add = jest.fn()
52+
const { result } = renderHook(() => useContextConsumer(), {
53+
wrapper: wrapWithVectorTileSourceContext({ id: 'id' })
54+
})
55+
expect(mapRef.sources.add).toHaveBeenCalledWith(result.current.dataSourceRef)
56+
})
57+
58+
it('should add event to data source', () => {
59+
mapRef.events.add = jest.fn()
60+
renderHook(() => useContextConsumer(), {
61+
wrapper: wrapWithVectorTileSourceContext({ id: 'id', events: { sourceadded: (source) => {} } })
62+
})
63+
expect(mapRef.events.add).toHaveBeenCalledWith(
64+
'sourceadded',
65+
expect.any(Object),
66+
expect.any(Function)
67+
)
68+
})
69+
})
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import atlas from 'azure-maps-control'
2+
import React, { useContext, useState } from 'react'
3+
import { useCheckRef } from '../hooks/useCheckRef'
4+
import { DataSourceType, IAzureMapsContextProps, IAzureMapSourceEventType, IAzureVectorTileSourceStatefulProviderProps, MapType } from '../types'
5+
import { AzureMapDataSourceRawProvider as Provider } from './AzureMapDataSourceContext'
6+
import { AzureMapsContext } from './AzureMapContext'
7+
8+
/**
9+
* @param id datasource identifier
10+
* @param children layer providers representing datasource layers
11+
* @param options vector tile datasource options: see atlas.VectorTileSourceOptions
12+
* @param events a object containing sourceadded, sourceremoved event handlers
13+
*/
14+
const AzureMapVectorTileSourceStatefulProvider = ({
15+
id,
16+
children,
17+
options,
18+
events = {},
19+
}: IAzureVectorTileSourceStatefulProviderProps) => {
20+
const [dataSourceRef] = useState<atlas.source.VectorTileSource>(new atlas.source.VectorTileSource(id, options))
21+
const { mapRef } = useContext<IAzureMapsContextProps>(AzureMapsContext)
22+
useCheckRef<MapType, DataSourceType>(mapRef, dataSourceRef, (mref, dref) => {
23+
for (const eventType in events) {
24+
const handler = events[eventType as IAzureMapSourceEventType] as (e: atlas.source.Source) => void | undefined
25+
if(handler) {
26+
mref.events.add(eventType as IAzureMapSourceEventType, dref, handler)
27+
}
28+
}
29+
mref.sources.add(dref)
30+
})
31+
32+
return (
33+
<Provider
34+
value={{
35+
dataSourceRef,
36+
}}
37+
>
38+
{mapRef && children}
39+
</Provider>
40+
)
41+
}
42+
43+
export { AzureMapVectorTileSourceStatefulProvider as AzureMapVectorTileSourceProvider }

src/hooks/useAzureMapLayer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import { MapType } from '../types'
1414

1515
export const constructLayer = (
1616
{ id, options = {}, type }: Omit<IAzureLayerStatefulProviderProps, 'onCreateCustomLayer'>,
17-
dataSourceRef: atlas.source.DataSource
17+
dataSourceRef: DataSourceType
1818
) => {
1919
switch (type) {
2020
case 'SymbolLayer':

src/react-azure-maps.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ export {
1010
export {
1111
AzureMapDataSourceContext,
1212
AzureMapDataSourceConsumer,
13-
AzureMapDataSourceProvider
13+
AzureMapDataSourceProvider,
1414
} from './contexts/AzureMapDataSourceContext'
15+
export { AzureMapVectorTileSourceProvider } from './contexts/AzureMapVectorTileSourceProvider'
1516
export {
1617
AzureMapLayerContext,
1718
AzureMapLayerConsumer,

src/types.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ import atlas, {
3232
UserInteractionOptions,
3333
Control,
3434
BubbleLayerOptions,
35-
LayerOptions
35+
LayerOptions,
36+
VectorTileSourceOptions
3637
} from 'azure-maps-control'
3738

3839
export type IAzureMapOptions = ServiceOptions &
@@ -120,7 +121,7 @@ export type IAzureMapPopup = {
120121
}
121122

122123
export type IAzureMapDataSourceContextState = {
123-
dataSourceRef: atlas.source.DataSource | null
124+
dataSourceRef: atlas.source.DataSource | atlas.source.VectorTileSource | null
124125
}
125126

126127
export type IAzureMapLayerContextState = {
@@ -132,10 +133,16 @@ export type IAzureDataSourceChildren =
132133
| ReactElement<IAzureMapFeature>
133134
| ReactElement<IAzureLayerStatefulProviderProps>
134135

136+
export type IAzureVectorTileSourceChildren = ReactElement<IAzureLayerStatefulProviderProps>
137+
135138
export type IAzureMapDataSourceEvent = {
136139
[property in IAzureMapDataSourceEventType]: (e: Shape[]) => void
137140
}
138141

142+
export type IAzureMapVectorTileSourceEvent = {
143+
[property in IAzureMapSourceEventType]?: (e: atlas.source.VectorTileSource) => void
144+
}
145+
139146
export type IAzureMapEvent = {
140147
[property in IAzureMapEventsType]: (
141148
e:
@@ -169,6 +176,17 @@ export type IAzureDataSourceStatefulProviderProps = {
169176
index?: number
170177
}
171178

179+
export type IAzureVectorTileSourceStatefulProviderProps = {
180+
id: string,
181+
children?: | Array<IAzureVectorTileSourceChildren | IAzureVectorTileSourceChildren[] | null>
182+
| IAzureVectorTileSourceChildren
183+
| null
184+
options?: VectorTileSourceOptions,
185+
events?: IAzureMapVectorTileSourceEvent
186+
// NOTE: not sure yet why this is needed, haven't seen this used in AzureMapsDataSource, though IAzureGeoJSONDataSourceStatefulProviderProps has it
187+
index?: number
188+
}
189+
172190
export type IAzureMapLayerEvent = {
173191
[property in IAzureMapLayerEventType]: (
174192
e: MapMouseEvent | MapTouchEvent | MapMouseWheelEvent
@@ -307,7 +325,7 @@ export type IAzureMapLayerProps = IAzureMapLayerContextState
307325
export type IAzureMapMouseEventRef = HtmlMarker // && other possible iterfaces
308326
export type IAzureMapsContextProps = IAzureMapContextState
309327
export type IAzureMapDataSourceProps = IAzureMapDataSourceContextState
310-
export type DataSourceType = atlas.source.DataSource
328+
export type DataSourceType = atlas.source.DataSource | atlas.source.VectorTileSource
311329
export type LayerType = atlas.layer.SymbolLayer | atlas.layer.ImageLayer | atlas.layer.TileLayer
312330
export type MapType = atlas.Map
313331
export type GeometryType = atlas.data.Geometry

0 commit comments

Comments
 (0)