@@ -7,6 +7,7 @@ const GeoJSONWrapper = require('./geojson_wrapper');
7
7
const vtpbf = require ( 'vt-pbf' ) ;
8
8
const supercluster = require ( 'supercluster' ) ;
9
9
const geojsonvt = require ( 'geojson-vt' ) ;
10
+ const assert = require ( 'assert' ) ;
10
11
11
12
const VectorTileWorkerSource = require ( './vector_tile_worker_source' ) ;
12
13
@@ -32,6 +33,10 @@ export type LoadGeoJSONParameters = {
32
33
geojsonVtOptions ?: Object
33
34
} ;
34
35
36
+ export type CoalesceParameters = {
37
+ source : string
38
+ } ;
39
+
35
40
export type LoadGeoJSON = ( params : LoadGeoJSONParameters , callback : Callback < mixed > ) => void ;
36
41
37
42
export interface GeoJSONIndex {
@@ -41,11 +46,11 @@ function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataC
41
46
const source = params . source ,
42
47
canonical = params . tileID . canonical ;
43
48
44
- if ( ! this . _geoJSONIndexes [ source ] ) {
49
+ if ( ! this . _sources [ source ] || ! this . _sources [ source ] . geoJSONIndex ) {
45
50
return callback ( null , null ) ; // we couldn't load the file
46
51
}
47
52
48
- const geoJSONTile = this . _geoJSONIndexes [ source ] . getTile ( canonical . z , canonical . x , canonical . y ) ;
53
+ const geoJSONTile = this . _sources [ source ] . geoJSONIndex . getTile ( canonical . z , canonical . x , canonical . y ) ;
49
54
if ( ! geoJSONTile ) {
50
55
return callback ( null , null ) ; // nothing in the given tile
51
56
}
@@ -67,6 +72,11 @@ function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataC
67
72
} ) ;
68
73
}
69
74
75
+ export type SourceState =
76
+ | 'Idle' // Source empty or data loaded
77
+ | 'Coalescing' // Data finished loading, but discard 'loadData' messages until receiving 'coalesced'
78
+ | 'NeedsLoadData' ; // 'loadData' received while coalescing, trigger one more 'loadData' on receiving 'coalesced'
79
+
70
80
/**
71
81
* The {@link WorkerSource} implementation that supports {@link GeoJSONSource}.
72
82
* This class is designed to be easily reused to support custom source types
@@ -78,8 +88,13 @@ function loadGeoJSONTile(params: WorkerTileParameters, callback: LoadVectorDataC
78
88
* @private
79
89
*/
80
90
class GeoJSONWorkerSource extends VectorTileWorkerSource {
81
- _geoJSONIndexes : { [ string ] : GeoJSONIndex } ;
82
91
loadGeoJSON : LoadGeoJSON ;
92
+ _sources : { [ string ] : {
93
+ state ?: SourceState ,
94
+ pendingCallback ?: Callback < boolean > ,
95
+ pendingLoadDataParams ?: LoadGeoJSONParameters ,
96
+ geoJSONIndex ?: GeoJSONIndex // object mapping source ids to geojson-vt-like tile indexes
97
+ } } ;
83
98
84
99
/**
85
100
* @param [loadGeoJSON] Optional method for custom loading/parsing of
@@ -91,8 +106,7 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
91
106
if ( loadGeoJSON ) {
92
107
this . loadGeoJSON = loadGeoJSON ;
93
108
}
94
- // object mapping source ids to geojson-vt-like tile indexes
95
- this . _geoJSONIndexes = { } ;
109
+ this . _sources = { } ;
96
110
}
97
111
98
112
/**
@@ -103,11 +117,51 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
103
117
* Defers to {@link GeoJSONWorkerSource#loadGeoJSON} for the fetching/parsing,
104
118
* expecting `callback(error, data)` to be called with either an error or a
105
119
* parsed GeoJSON object.
120
+ *
121
+ * When `loadData` requests come in faster than they can be processed,
122
+ * they are coalesced into a single request using the latest data.
123
+ * See {@link GeoJSONWorkerSource#coalesce}
124
+ *
106
125
* @param params
107
126
* @param params.source The id of the source.
108
127
* @param callback
109
128
*/
110
- loadData ( params : LoadGeoJSONParameters , callback : Callback < { [ string ] : { [ string ] : Array < PerformanceResourceTiming > } } > ) {
129
+ loadData ( params : LoadGeoJSONParameters , callback : Callback < boolean > ) {
130
+ if ( ! this . _sources [ params . source ] ) {
131
+ this . _sources [ params . source ] = { } ;
132
+ }
133
+ const source = this . _sources [ params . source ] ;
134
+
135
+ if ( source . pendingCallback ) {
136
+ // Tell the foreground the previous call has been abandoned
137
+ source . pendingCallback ( null , true ) ;
138
+ }
139
+ source . pendingCallback = callback ;
140
+ source . pendingLoadDataParams = params ;
141
+
142
+ if ( source . state &&
143
+ source . state !== 'Idle' ) {
144
+ source . state = 'NeedsLoadData' ;
145
+ } else {
146
+ source . state = 'Coalescing' ;
147
+ this . _loadData ( params . source ) ;
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Internal implementation: called directly by `loadData`
153
+ * or by `coalesce` using stored parameters.
154
+ */
155
+ _loadData ( sourceId : string ) {
156
+ const source = this . _sources [ sourceId ] ;
157
+ if ( ! source . pendingCallback || ! source . pendingLoadDataParams ) {
158
+ assert ( false ) ;
159
+ return ;
160
+ }
161
+ const callback = source . pendingCallback ;
162
+ const params = source . pendingLoadDataParams ;
163
+ delete source . pendingCallback ;
164
+ delete source . pendingLoadDataParams ;
111
165
this . loadGeoJSON ( params , ( err , data ) => {
112
166
if ( err || ! data ) {
113
167
return callback ( err ) ;
@@ -117,7 +171,7 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
117
171
rewind ( data , true ) ;
118
172
119
173
try {
120
- this . _geoJSONIndexes [ params . source ] = params . cluster ?
174
+ source . geoJSONIndex = params . cluster ?
121
175
supercluster ( params . superclusterOptions ) . load ( data . features ) :
122
176
geojsonvt ( data , params . geojsonVtOptions ) ;
123
177
} catch ( err ) {
@@ -141,6 +195,39 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
141
195
} ) ;
142
196
}
143
197
198
+ /**
199
+ * While processing `loadData`, we coalesce all further
200
+ * `loadData` messages into a single call to _loadData
201
+ * that will happen once we've finished processing the
202
+ * first message. {@link GeoJSONSource#_updateWorkerData}
203
+ * is responsible for sending us the `coalesce` message
204
+ * at the time it receives a response from `loadData`
205
+ *
206
+ * State: Idle
207
+ * ↑ |
208
+ * 'coalesce' 'loadData'
209
+ * | (triggers load)
210
+ * | ↓
211
+ * State: Coalescing
212
+ * ↑ |
213
+ * (triggers load) |
214
+ * 'coalesce' 'loadData'
215
+ * | ↓
216
+ * State: NeedsLoadData
217
+ */
218
+ coalesce ( params : CoalesceParameters ) {
219
+ const source = this . _sources [ params . source ] ;
220
+ if ( ! source ) {
221
+ return ; // coalesce queued after removeSource
222
+ }
223
+ if ( source . state === 'Coalescing' ) {
224
+ source . state = 'Idle' ;
225
+ } else if ( source . state === 'NeedsLoadData' ) {
226
+ source . state = 'Coalescing' ;
227
+ this . _loadData ( params . source ) ;
228
+ }
229
+ }
230
+
144
231
/**
145
232
* Implements {@link WorkerSource#reloadTile}.
146
233
*
@@ -192,8 +279,13 @@ class GeoJSONWorkerSource extends VectorTileWorkerSource {
192
279
}
193
280
194
281
removeSource ( params : { source : string } , callback : Callback < mixed > ) {
195
- if ( this . _geoJSONIndexes [ params . source ] ) {
196
- delete this . _geoJSONIndexes [ params . source ] ;
282
+ const removedSource = this . _sources [ params . source ] ;
283
+ if ( removedSource ) {
284
+ if ( removedSource . pendingCallback ) {
285
+ // Don't leak callbacks
286
+ removedSource . pendingCallback ( null , true ) ;
287
+ }
288
+ delete this . _sources [ params . source ] ;
197
289
}
198
290
callback ( ) ;
199
291
}
0 commit comments