@igo2/geo 21.0.0-next.2 → 21.0.0-next.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/igo2-geo-offline.mjs +710 -0
- package/fesm2022/igo2-geo-offline.mjs.map +1 -0
- package/fesm2022/igo2-geo.mjs +12406 -12684
- package/fesm2022/igo2-geo.mjs.map +1 -1
- package/locale/en.geo.json +5 -0
- package/locale/fr.geo.json +5 -0
- package/package.json +11 -6
- package/types/igo2-geo-offline.d.ts +190 -0
- package/types/igo2-geo.d.ts +2546 -2578
|
@@ -0,0 +1,710 @@
|
|
|
1
|
+
import { Compression, ObjectUtils } from '@igo2/utils';
|
|
2
|
+
import { from, of, switchMap, first, concatMap, map, mergeMap, zip, forkJoin, defer, EMPTY, fromEvent, merge, combineLatest } from 'rxjs';
|
|
3
|
+
import { openDB } from 'idb';
|
|
4
|
+
import { HttpClient } from '@angular/common/http';
|
|
5
|
+
import * as i0 from '@angular/core';
|
|
6
|
+
import { inject, Injectable, Injector, makeEnvironmentProviders, provideAppInitializer } from '@angular/core';
|
|
7
|
+
import { MessageService } from '@igo2/core/message';
|
|
8
|
+
import JSZip from 'jszip';
|
|
9
|
+
import { catchError, concatMap as concatMap$1, first as first$1, delay, tap, switchMap as switchMap$1, debounceTime, map as map$1 } from 'rxjs/operators';
|
|
10
|
+
import { NetworkService } from '@igo2/core/network';
|
|
11
|
+
import { isOlFlatStyleLike, LayerService, LAYER_PERSISTENCE, OFFLINE_LAYER_RESTORE, VECTOR_LAYER_EXTENSIONS } from '@igo2/geo';
|
|
12
|
+
import * as olformat from 'ol/format';
|
|
13
|
+
import { ConfigService } from '@igo2/core/config';
|
|
14
|
+
|
|
15
|
+
var InsertSourceInsertDBEnum;
|
|
16
|
+
(function (InsertSourceInsertDBEnum) {
|
|
17
|
+
InsertSourceInsertDBEnum["System"] = "system";
|
|
18
|
+
InsertSourceInsertDBEnum["User"] = "user";
|
|
19
|
+
})(InsertSourceInsertDBEnum || (InsertSourceInsertDBEnum = {}));
|
|
20
|
+
|
|
21
|
+
function createIndexedDb() {
|
|
22
|
+
return openDB('igo2DB', undefined, {
|
|
23
|
+
upgrade(db) {
|
|
24
|
+
const geoDataStore = db.createObjectStore('geoData', {
|
|
25
|
+
keyPath: 'url',
|
|
26
|
+
autoIncrement: false
|
|
27
|
+
});
|
|
28
|
+
geoDataStore.createIndex('regionID-idx', 'regionID', {
|
|
29
|
+
unique: false
|
|
30
|
+
});
|
|
31
|
+
db.createObjectStore('layerData', {
|
|
32
|
+
keyPath: 'layerId',
|
|
33
|
+
autoIncrement: false
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
class GeoDB {
|
|
40
|
+
db$;
|
|
41
|
+
compression = new Compression();
|
|
42
|
+
collisionsMap = new Map();
|
|
43
|
+
_newData = 0;
|
|
44
|
+
revertObservablesSubscription;
|
|
45
|
+
constructor() {
|
|
46
|
+
this.db$ = from(createIndexedDb());
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Only blob can be compressed
|
|
50
|
+
* @param url
|
|
51
|
+
* @param regionID
|
|
52
|
+
* @param object object to handle
|
|
53
|
+
* @param insertSource type of event user or system
|
|
54
|
+
* @param insertEvent Name of the event where the insert has been triggered
|
|
55
|
+
* @returns
|
|
56
|
+
*/
|
|
57
|
+
update(url, regionID, object, insertSource, insertEvent) {
|
|
58
|
+
const object$ = (object instanceof Blob
|
|
59
|
+
? this.compression.compressBlob(object)
|
|
60
|
+
: of(object));
|
|
61
|
+
const compress = object instanceof Blob ? true : false;
|
|
62
|
+
let geoDBData;
|
|
63
|
+
const a = this.db$.pipe(switchMap((db) => {
|
|
64
|
+
return object$.pipe(first(), switchMap((object) => {
|
|
65
|
+
geoDBData = {
|
|
66
|
+
url,
|
|
67
|
+
regionID,
|
|
68
|
+
object: object,
|
|
69
|
+
compressed: compress,
|
|
70
|
+
insertSource,
|
|
71
|
+
insertEvent
|
|
72
|
+
};
|
|
73
|
+
return this.getGeoDBData(url);
|
|
74
|
+
}), concatMap((res) => {
|
|
75
|
+
const dbObject = res;
|
|
76
|
+
if (!dbObject) {
|
|
77
|
+
this._newData++;
|
|
78
|
+
return from(db.add('geoData', geoDBData)).pipe(map(() => geoDBData));
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
const currentRegionID = dbObject.regionID;
|
|
82
|
+
if (currentRegionID !== regionID) {
|
|
83
|
+
const collisions = this.collisionsMap.get(currentRegionID);
|
|
84
|
+
if (collisions !== undefined) {
|
|
85
|
+
collisions.push(dbObject.url);
|
|
86
|
+
this.collisionsMap.set(currentRegionID, collisions);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
this.collisionsMap.set(currentRegionID, [dbObject.url]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return this.customUpdate(geoDBData);
|
|
93
|
+
}
|
|
94
|
+
}));
|
|
95
|
+
}));
|
|
96
|
+
return a;
|
|
97
|
+
}
|
|
98
|
+
customUpdate(geoDBData) {
|
|
99
|
+
const b = this.delete(geoDBData.url).pipe(mergeMap(() => this.add(geoDBData)));
|
|
100
|
+
return b;
|
|
101
|
+
}
|
|
102
|
+
add(geoDBData) {
|
|
103
|
+
return this.db$.pipe(switchMap((db) => from(db?.add('geoData', geoDBData)).pipe(map(() => geoDBData))));
|
|
104
|
+
}
|
|
105
|
+
put(geoDBData) {
|
|
106
|
+
return this.db$.pipe(switchMap((db) => from(db?.put('geoData', geoDBData)).pipe(map(() => geoDBData))));
|
|
107
|
+
}
|
|
108
|
+
getGeoDBData(url) {
|
|
109
|
+
return this.db$.pipe(switchMap((db) => from(db?.get('geoData', url))));
|
|
110
|
+
}
|
|
111
|
+
get(url) {
|
|
112
|
+
return this.getGeoDBData(url).pipe(mergeMap((data) => {
|
|
113
|
+
const rObj = !data?.compressed
|
|
114
|
+
? data?.object
|
|
115
|
+
: this.compression.decompressBlob(data?.object);
|
|
116
|
+
return of(rObj);
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
delete(key) {
|
|
120
|
+
return this.db$.pipe(switchMap((db) => from(db?.delete('geoData', key))), map(() => {
|
|
121
|
+
{
|
|
122
|
+
return { key };
|
|
123
|
+
}
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
getRegionCountByID(id) {
|
|
127
|
+
return this.getRegionByID(id).pipe(switchMap((datas) => {
|
|
128
|
+
return of(datas.length);
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
getRegionByID(id) {
|
|
132
|
+
if (!id) {
|
|
133
|
+
return of([]);
|
|
134
|
+
}
|
|
135
|
+
const IDBKey = IDBKeyRange.only(id);
|
|
136
|
+
return this.db$.pipe(switchMap((db) => from(db?.getAllFromIndex('geoData', 'regionID-idx', IDBKey))));
|
|
137
|
+
}
|
|
138
|
+
deleteByRegionID(id) {
|
|
139
|
+
if (!id) {
|
|
140
|
+
return of([]);
|
|
141
|
+
}
|
|
142
|
+
return this.db$.pipe(switchMap((db) => {
|
|
143
|
+
const tx = db.transaction('geoData', 'readwrite');
|
|
144
|
+
return of(tx);
|
|
145
|
+
}), mergeMap((tx) => this.getRegionByID(id).pipe(concatMap((datas) => {
|
|
146
|
+
const promises = datas.map((data) => tx.store.delete(data.url));
|
|
147
|
+
promises.push(tx.done);
|
|
148
|
+
return from(Promise.all(promises));
|
|
149
|
+
}))));
|
|
150
|
+
}
|
|
151
|
+
resetCounters() {
|
|
152
|
+
this.resetCollisionsMap();
|
|
153
|
+
this._newData = 0;
|
|
154
|
+
}
|
|
155
|
+
resetCollisionsMap() {
|
|
156
|
+
this.collisionsMap = new Map();
|
|
157
|
+
}
|
|
158
|
+
revertCollisions() {
|
|
159
|
+
if (this.revertObservablesSubscription) {
|
|
160
|
+
this.revertObservablesSubscription.unsubscribe();
|
|
161
|
+
}
|
|
162
|
+
const revertObservables = [];
|
|
163
|
+
for (const [regionID, collisions] of this.collisionsMap) {
|
|
164
|
+
for (const url of collisions) {
|
|
165
|
+
revertObservables.push(this.getGeoDBData(url).pipe(first(), concatMap((dbObject) => {
|
|
166
|
+
const updatedObject = dbObject;
|
|
167
|
+
updatedObject.regionID = regionID;
|
|
168
|
+
return this.customUpdate(updatedObject);
|
|
169
|
+
})));
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
this.revertObservablesSubscription = zip(...revertObservables).subscribe();
|
|
173
|
+
}
|
|
174
|
+
get newData() {
|
|
175
|
+
return this._newData;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Delay before dismissing the "download completed" toast, so it stays legible for a moment. */
|
|
180
|
+
const DOWNLOAD_COMPLETED_DELAY_MS = 2500;
|
|
181
|
+
const TOAST_TIMEOUT_MS = 40000;
|
|
182
|
+
class GeoDataSyncService {
|
|
183
|
+
http = inject(HttpClient);
|
|
184
|
+
messageService = inject(MessageService);
|
|
185
|
+
load(urlFile) {
|
|
186
|
+
const geoDB = new GeoDB();
|
|
187
|
+
const downloadState = {};
|
|
188
|
+
this.http
|
|
189
|
+
.get(urlFile)
|
|
190
|
+
.pipe(catchError((error) => this.handleConfigFileError(urlFile, error)), concatMap$1((datasToIDB) => this.processGeoDatas(geoDB, datasToIDB?.geoDatas ?? [], downloadState)))
|
|
191
|
+
.subscribe(() => this.notifyDownloadCompleted(downloadState));
|
|
192
|
+
}
|
|
193
|
+
handleConfigFileError(urlFile, error) {
|
|
194
|
+
this.messageService.error(`GeoData file ${urlFile} could not be read`);
|
|
195
|
+
error.error.caught = true;
|
|
196
|
+
throw error;
|
|
197
|
+
}
|
|
198
|
+
processGeoDatas(geoDB, geoDatas, downloadState) {
|
|
199
|
+
const currentDate = new Date();
|
|
200
|
+
const operations = [];
|
|
201
|
+
for (const geoData of geoDatas) {
|
|
202
|
+
if (typeof geoData.triggerDate === 'string') {
|
|
203
|
+
geoData.triggerDate = new Date(Date.parse(geoData.triggerDate));
|
|
204
|
+
}
|
|
205
|
+
if (currentDate < geoData.triggerDate) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (geoData.action === 'update') {
|
|
209
|
+
operations.push(...this.updateGeoData(geoDB, geoData, downloadState));
|
|
210
|
+
}
|
|
211
|
+
else if (geoData.action === 'delete') {
|
|
212
|
+
operations.push(...geoData.urls.map((url) => geoDB.delete(url)));
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return forkJoin(operations);
|
|
216
|
+
}
|
|
217
|
+
updateGeoData(geoDB, geoData, downloadState) {
|
|
218
|
+
const insertEvent = `${geoData.source || InsertSourceInsertDBEnum.System} (${geoData.triggerDate})`;
|
|
219
|
+
return geoData.urls.map((url) => geoDB
|
|
220
|
+
.getGeoDBData(url)
|
|
221
|
+
.pipe(concatMap$1((existing) => existing?.insertEvent === insertEvent
|
|
222
|
+
? of(false)
|
|
223
|
+
: this.downloadAndStore(geoDB, url, geoData, insertEvent, downloadState))));
|
|
224
|
+
}
|
|
225
|
+
downloadAndStore(geoDB, url, geoData, insertEvent, downloadState) {
|
|
226
|
+
this.showDownloadStartMessage(downloadState);
|
|
227
|
+
const isZip = this.isZip(url);
|
|
228
|
+
const download$ = isZip
|
|
229
|
+
? this.http.get(url, { responseType: 'arraybuffer' })
|
|
230
|
+
: this.http.get(url, { responseType: 'json' });
|
|
231
|
+
return download$.pipe(catchError((error) => this.handleDownloadError(downloadState, error)), concatMap$1((response) => isZip
|
|
232
|
+
? this.storeZippedGeoData(geoDB, response, url, geoData, insertEvent)
|
|
233
|
+
: geoDB.update(url, url, response, InsertSourceInsertDBEnum.System, insertEvent)));
|
|
234
|
+
}
|
|
235
|
+
handleDownloadError(downloadState, error) {
|
|
236
|
+
if (downloadState.toast) {
|
|
237
|
+
this.messageService.remove(downloadState.toast.toastId);
|
|
238
|
+
}
|
|
239
|
+
this.messageService.error('igo.geo.indexedDb.data-download-failed', undefined, { timeOut: TOAST_TIMEOUT_MS });
|
|
240
|
+
error.error.caught = true;
|
|
241
|
+
throw error;
|
|
242
|
+
}
|
|
243
|
+
storeZippedGeoData(geoDB, archive, url, geoData, insertEvent) {
|
|
244
|
+
return from(this.extractZipGeojsonEntries(archive, geoData.zippedBaseUrl)).pipe(concatMap$1((entries) => forkJoin([
|
|
245
|
+
// the archive itself is tracked so future loads can detect it was already processed
|
|
246
|
+
geoDB.update(url, url, {}, InsertSourceInsertDBEnum.System, insertEvent),
|
|
247
|
+
...entries.map(({ zippedUrl, geojson }) => geoDB.update(zippedUrl, url, geojson, InsertSourceInsertDBEnum.System, insertEvent))
|
|
248
|
+
])));
|
|
249
|
+
}
|
|
250
|
+
async extractZipGeojsonEntries(archive, zippedBaseUrl = '') {
|
|
251
|
+
const zipped = await JSZip.loadAsync(archive);
|
|
252
|
+
const baseUrl = zippedBaseUrl.endsWith('/')
|
|
253
|
+
? zippedBaseUrl
|
|
254
|
+
: `${zippedBaseUrl}/`;
|
|
255
|
+
const geojsonPaths = Object.keys(zipped.files).filter((relativePath) => relativePath.toLowerCase().endsWith('.geojson'));
|
|
256
|
+
return Promise.all(geojsonPaths.map(async (relativePath) => ({
|
|
257
|
+
zippedUrl: `${baseUrl}${relativePath}`,
|
|
258
|
+
geojson: JSON.parse(await zipped.file(relativePath).async('text'))
|
|
259
|
+
})));
|
|
260
|
+
}
|
|
261
|
+
showDownloadStartMessage(downloadState) {
|
|
262
|
+
if (downloadState.toast) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
downloadState.toast = this.messageService.info('igo.geo.indexedDb.data-download-start', undefined, {
|
|
266
|
+
disableTimeOut: true,
|
|
267
|
+
progressBar: false,
|
|
268
|
+
closeButton: true,
|
|
269
|
+
tapToDismiss: false
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
notifyDownloadCompleted(downloadState) {
|
|
273
|
+
if (!downloadState.toast) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const toast = downloadState.toast;
|
|
277
|
+
setTimeout(() => {
|
|
278
|
+
this.messageService.remove(toast.toastId);
|
|
279
|
+
this.messageService.success('igo.geo.indexedDb.data-download-completed', undefined, { timeOut: TOAST_TIMEOUT_MS });
|
|
280
|
+
}, DOWNLOAD_COMPLETED_DELAY_MS);
|
|
281
|
+
}
|
|
282
|
+
isZip(value) {
|
|
283
|
+
const regex = /(zip)$/;
|
|
284
|
+
return typeof value === 'string' && regex.test(value.toLowerCase());
|
|
285
|
+
}
|
|
286
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: GeoDataSyncService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
287
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: GeoDataSyncService });
|
|
288
|
+
}
|
|
289
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: GeoDataSyncService, decorators: [{
|
|
290
|
+
type: Injectable
|
|
291
|
+
}] });
|
|
292
|
+
|
|
293
|
+
class LayerDB {
|
|
294
|
+
dbPromise;
|
|
295
|
+
constructor() {
|
|
296
|
+
this.dbPromise = createIndexedDb();
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* This method allow to update the stored layer into the indexeddb (layerData)
|
|
300
|
+
* @param layerDBData
|
|
301
|
+
* @returns
|
|
302
|
+
*/
|
|
303
|
+
update(layerDBData) {
|
|
304
|
+
return from(this.updateAsync(layerDBData));
|
|
305
|
+
}
|
|
306
|
+
async updateAsync(layerDBData) {
|
|
307
|
+
const db = await this.dbPromise;
|
|
308
|
+
const existing = await db.get('layerData', layerDBData.layerId);
|
|
309
|
+
if (existing) {
|
|
310
|
+
await db.delete('layerData', layerDBData.layerId);
|
|
311
|
+
}
|
|
312
|
+
await db.add('layerData', layerDBData);
|
|
313
|
+
return layerDBData;
|
|
314
|
+
}
|
|
315
|
+
add(layerDBData) {
|
|
316
|
+
return from(this.addAsync(layerDBData));
|
|
317
|
+
}
|
|
318
|
+
async addAsync(layerDBData) {
|
|
319
|
+
const db = await this.dbPromise;
|
|
320
|
+
await db.add('layerData', layerDBData);
|
|
321
|
+
return layerDBData;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* This method retrieve an idb layer definition
|
|
325
|
+
* @param layerId
|
|
326
|
+
* @returns
|
|
327
|
+
*/
|
|
328
|
+
getByID(layerId) {
|
|
329
|
+
return from(this.getByIDAsync(layerId));
|
|
330
|
+
}
|
|
331
|
+
async getByIDAsync(layerId) {
|
|
332
|
+
const db = await this.dbPromise;
|
|
333
|
+
return db.get('layerData', layerId.toString());
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* This method delete an idb layer definition
|
|
337
|
+
* @param key
|
|
338
|
+
* @returns
|
|
339
|
+
*/
|
|
340
|
+
delete(key) {
|
|
341
|
+
return from(this.deleteAsync(key));
|
|
342
|
+
}
|
|
343
|
+
async deleteAsync(key) {
|
|
344
|
+
const db = await this.dbPromise;
|
|
345
|
+
await db.delete('layerData', key);
|
|
346
|
+
return { key };
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* This method retrive all idb layer definition
|
|
350
|
+
* @param layerId
|
|
351
|
+
* @returns
|
|
352
|
+
*/
|
|
353
|
+
getAll() {
|
|
354
|
+
return from(this.getAllAsync());
|
|
355
|
+
}
|
|
356
|
+
async getAllAsync() {
|
|
357
|
+
const db = await this.dbPromise;
|
|
358
|
+
return db.getAll('layerData');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
class GeoNetworkService {
|
|
363
|
+
http = inject(HttpClient);
|
|
364
|
+
networkService = inject(NetworkService);
|
|
365
|
+
networkOnline = true;
|
|
366
|
+
constructor() {
|
|
367
|
+
this.networkService.currentState().subscribe((state) => {
|
|
368
|
+
this.networkOnline = state.connection;
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
get(url, simpleGetOptions) {
|
|
372
|
+
if (window.navigator.onLine && this.networkOnline) {
|
|
373
|
+
return this.getOnline(url, simpleGetOptions);
|
|
374
|
+
}
|
|
375
|
+
return this.getOffline(url);
|
|
376
|
+
}
|
|
377
|
+
getOnline(url, simpleGetOptions) {
|
|
378
|
+
let request;
|
|
379
|
+
switch (simpleGetOptions.responseType) {
|
|
380
|
+
case 'arraybuffer':
|
|
381
|
+
request = this.http.get(url, {
|
|
382
|
+
responseType: 'arraybuffer',
|
|
383
|
+
withCredentials: simpleGetOptions.withCredentials
|
|
384
|
+
});
|
|
385
|
+
break;
|
|
386
|
+
case 'text':
|
|
387
|
+
request = this.http.get(url, {
|
|
388
|
+
responseType: 'text',
|
|
389
|
+
withCredentials: simpleGetOptions.withCredentials
|
|
390
|
+
});
|
|
391
|
+
break;
|
|
392
|
+
case 'json':
|
|
393
|
+
request = this.http.get(url, {
|
|
394
|
+
responseType: 'json',
|
|
395
|
+
withCredentials: simpleGetOptions.withCredentials
|
|
396
|
+
});
|
|
397
|
+
break;
|
|
398
|
+
default:
|
|
399
|
+
request = this.http.get(url, {
|
|
400
|
+
responseType: 'blob',
|
|
401
|
+
withCredentials: simpleGetOptions.withCredentials
|
|
402
|
+
});
|
|
403
|
+
break;
|
|
404
|
+
}
|
|
405
|
+
return request;
|
|
406
|
+
}
|
|
407
|
+
getOffline(url) {
|
|
408
|
+
const geoDB = new GeoDB();
|
|
409
|
+
return geoDB?.get(url);
|
|
410
|
+
}
|
|
411
|
+
isOnline() {
|
|
412
|
+
return this.networkOnline && window.navigator.onLine;
|
|
413
|
+
}
|
|
414
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: GeoNetworkService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
415
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: GeoNetworkService });
|
|
416
|
+
}
|
|
417
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: GeoNetworkService, decorators: [{
|
|
418
|
+
type: Injectable
|
|
419
|
+
}], ctorParameters: () => [] });
|
|
420
|
+
|
|
421
|
+
class IndexedDbLayerPersistenceService {
|
|
422
|
+
geoDB = inject(GeoDB);
|
|
423
|
+
layerDB = inject(LayerDB);
|
|
424
|
+
isPersistent(layer) {
|
|
425
|
+
if (layer.type !== 'vector') {
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
const options = layer.options;
|
|
429
|
+
return options.offline?.enabled === true;
|
|
430
|
+
}
|
|
431
|
+
removePersistedData(layer) {
|
|
432
|
+
if (!this.isPersistent(layer) || layer.id === undefined) {
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
const options = layer.options;
|
|
436
|
+
const layerId = layer.id.toString();
|
|
437
|
+
const sourceUrl = options.sourceOptions?.url;
|
|
438
|
+
const featureStorageKey = typeof sourceUrl === 'string' && sourceUrl.length > 0
|
|
439
|
+
? sourceUrl
|
|
440
|
+
: layerId;
|
|
441
|
+
forkJoin([
|
|
442
|
+
this.geoDB.delete(featureStorageKey),
|
|
443
|
+
this.layerDB.delete(layerId)
|
|
444
|
+
]).subscribe();
|
|
445
|
+
}
|
|
446
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: IndexedDbLayerPersistenceService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
447
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: IndexedDbLayerPersistenceService });
|
|
448
|
+
}
|
|
449
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: IndexedDbLayerPersistenceService, decorators: [{
|
|
450
|
+
type: Injectable
|
|
451
|
+
}] });
|
|
452
|
+
|
|
453
|
+
class IndexedDbVectorLayerExtensionInstance {
|
|
454
|
+
context;
|
|
455
|
+
geoDB;
|
|
456
|
+
layerDB;
|
|
457
|
+
geoNetworkService;
|
|
458
|
+
subscriptions = [];
|
|
459
|
+
constructor(context, geoDB, layerDB, geoNetworkService) {
|
|
460
|
+
this.context = context;
|
|
461
|
+
this.geoDB = geoDB;
|
|
462
|
+
this.layerDB = layerDB;
|
|
463
|
+
this.geoNetworkService = geoNetworkService;
|
|
464
|
+
this.registerPersistence();
|
|
465
|
+
}
|
|
466
|
+
interceptLoad(request, next) {
|
|
467
|
+
if (typeof request.url === 'function') {
|
|
468
|
+
next(request);
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const requestUrl = request.url;
|
|
472
|
+
const storageKey = this.context.options.sourceOptions?.url ||
|
|
473
|
+
this.context.id?.toString() ||
|
|
474
|
+
requestUrl;
|
|
475
|
+
const responseType = this.context.source
|
|
476
|
+
.getFormat()
|
|
477
|
+
.getType();
|
|
478
|
+
const getVectorObs$ = defer(() => this.geoNetworkService.get(requestUrl, { responseType })).pipe(first$1());
|
|
479
|
+
const idbGetVectorObs$ = this.geoDB.get(storageKey).pipe(catchError(() => of(undefined)), delay(750), concatMap$1((response) => (response ? of(response) : getVectorObs$)), tap((content) => this.handleOnLoad(request, content)), catchError(() => {
|
|
480
|
+
request.source.removeLoadedExtent(request.extent);
|
|
481
|
+
request.failure();
|
|
482
|
+
return EMPTY;
|
|
483
|
+
}));
|
|
484
|
+
this.subscriptions.push(idbGetVectorObs$.subscribe());
|
|
485
|
+
}
|
|
486
|
+
destroy() {
|
|
487
|
+
this.subscriptions.forEach((subscription) => subscription.unsubscribe());
|
|
488
|
+
this.subscriptions = [];
|
|
489
|
+
}
|
|
490
|
+
registerPersistence() {
|
|
491
|
+
const sourceReady$ = fromEvent(this.context.layer, 'sourceready');
|
|
492
|
+
const featurePersistSubscription = sourceReady$
|
|
493
|
+
.pipe(tap(() => {
|
|
494
|
+
if (this.context.source.getFeatures().length > 0) {
|
|
495
|
+
this.maintainFeaturesInIdb();
|
|
496
|
+
}
|
|
497
|
+
this.maintainOptionsInIdb();
|
|
498
|
+
}), switchMap$1(() => merge(fromEvent(this.context.source, 'featuresloadend'), fromEvent(this.context.source, 'addfeature'), fromEvent(this.context.source, 'changefeature'), fromEvent(this.context.source, 'clear'), fromEvent(this.context.source, 'removefeature'))))
|
|
499
|
+
.pipe(debounceTime(750))
|
|
500
|
+
.subscribe(() => this.maintainFeaturesInIdb());
|
|
501
|
+
const optionPersistSubscription = sourceReady$
|
|
502
|
+
.pipe(switchMap$1(() => merge(fromEvent(this.context.layer, 'change'), fromEvent(this.context.layer, 'change:visible'), fromEvent(this.context.layer, 'change:opacity'), fromEvent(this.context.layer, 'change:zIndex'))))
|
|
503
|
+
.pipe(debounceTime(750))
|
|
504
|
+
.subscribe(() => this.maintainOptionsInIdb());
|
|
505
|
+
this.subscriptions.push(featurePersistSubscription, optionPersistSubscription);
|
|
506
|
+
}
|
|
507
|
+
maintainOptionsInIdb() {
|
|
508
|
+
if (this.context.id === undefined) {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
const id = this.context.id;
|
|
512
|
+
const options = this.context.options;
|
|
513
|
+
const offline = this.getOfflineState(options);
|
|
514
|
+
const layerData = ObjectUtils.removeUndefined({
|
|
515
|
+
layerId: id,
|
|
516
|
+
detailedContextUri: offline.contextUri,
|
|
517
|
+
sourceOptions: {
|
|
518
|
+
id,
|
|
519
|
+
type: 'vector',
|
|
520
|
+
queryable: true,
|
|
521
|
+
url: options.sourceOptions?.url
|
|
522
|
+
},
|
|
523
|
+
layerOptions: {
|
|
524
|
+
workspace: options.workspace,
|
|
525
|
+
zIndex: this.context.layer.getZIndex() ?? 1000000,
|
|
526
|
+
id,
|
|
527
|
+
isIgoInternalLayer: options.isIgoInternalLayer,
|
|
528
|
+
title: options.title,
|
|
529
|
+
visible: this.context.layer.getVisible(),
|
|
530
|
+
opacity: this.context.layer.getOpacity(),
|
|
531
|
+
style: isOlFlatStyleLike(options.style) ? options.style : undefined,
|
|
532
|
+
offline: Object.assign({}, options.offline, {
|
|
533
|
+
enabled: true,
|
|
534
|
+
contextUri: offline.contextUri
|
|
535
|
+
})
|
|
536
|
+
},
|
|
537
|
+
insertEvent: `${options.title}-${id}-${new Date()}`
|
|
538
|
+
});
|
|
539
|
+
this.layerDB.update(layerData).pipe(first$1()).subscribe();
|
|
540
|
+
}
|
|
541
|
+
maintainFeaturesInIdb() {
|
|
542
|
+
if (this.context.id === undefined) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
const sourceFeatures = this.context.source.getFeatures().map((feature) => {
|
|
546
|
+
const persistedFeature = feature.clone();
|
|
547
|
+
persistedFeature.setId(feature.getId());
|
|
548
|
+
persistedFeature.unset('_featureStore', true);
|
|
549
|
+
return persistedFeature;
|
|
550
|
+
});
|
|
551
|
+
const geojsonObject = JSON.parse(new olformat.GeoJSON().writeFeatures(sourceFeatures, {
|
|
552
|
+
dataProjection: 'EPSG:4326',
|
|
553
|
+
featureProjection: this.context.source.getProjection() || 'EPSG:3857'
|
|
554
|
+
}));
|
|
555
|
+
this.geoDB
|
|
556
|
+
.update(this.context.options.sourceOptions?.url || this.context.id.toString(), this.context.id, geojsonObject, InsertSourceInsertDBEnum.User, `${this.context.options.title}-${this.context.id}-${new Date()}`)
|
|
557
|
+
.pipe(first$1())
|
|
558
|
+
.subscribe();
|
|
559
|
+
}
|
|
560
|
+
handleOnLoad(request, content) {
|
|
561
|
+
const format = request.source.getFormat();
|
|
562
|
+
const type = format.getType();
|
|
563
|
+
let source;
|
|
564
|
+
switch (type) {
|
|
565
|
+
case 'xml':
|
|
566
|
+
source = new DOMParser().parseFromString(content.toString(), 'application/xml');
|
|
567
|
+
break;
|
|
568
|
+
case 'json':
|
|
569
|
+
case 'text':
|
|
570
|
+
case 'arraybuffer':
|
|
571
|
+
default:
|
|
572
|
+
source = content;
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
const readOptions = {
|
|
576
|
+
extent: request.extent,
|
|
577
|
+
featureProjection: request.projection
|
|
578
|
+
};
|
|
579
|
+
const features = format.readFeatures(source, readOptions);
|
|
580
|
+
if (features) {
|
|
581
|
+
request.source.addFeatures(features);
|
|
582
|
+
request.success(features);
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
request.success([]);
|
|
586
|
+
}
|
|
587
|
+
getOfflineState(options) {
|
|
588
|
+
return {
|
|
589
|
+
enabled: options.offline?.enabled === true,
|
|
590
|
+
contextUri: options.offline?.contextUri
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
class IndexedDbVectorLayerExtension {
|
|
595
|
+
id = 'indexed-db';
|
|
596
|
+
priority = 100;
|
|
597
|
+
geoDB = inject(GeoDB);
|
|
598
|
+
layerDB = inject(LayerDB);
|
|
599
|
+
geoNetworkService = inject(GeoNetworkService);
|
|
600
|
+
supports(options) {
|
|
601
|
+
return options.offline?.enabled === true;
|
|
602
|
+
}
|
|
603
|
+
attach(context) {
|
|
604
|
+
return new IndexedDbVectorLayerExtensionInstance(context, this.geoDB, this.layerDB, this.geoNetworkService);
|
|
605
|
+
}
|
|
606
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: IndexedDbVectorLayerExtension, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
607
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: IndexedDbVectorLayerExtension });
|
|
608
|
+
}
|
|
609
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: IndexedDbVectorLayerExtension, decorators: [{
|
|
610
|
+
type: Injectable
|
|
611
|
+
}] });
|
|
612
|
+
|
|
613
|
+
const OfflineFeatureKind = ['IndexedDb'];
|
|
614
|
+
|
|
615
|
+
class OfflineLayerRestoreService {
|
|
616
|
+
layerDB = inject(LayerDB);
|
|
617
|
+
injector = inject(Injector);
|
|
618
|
+
createAsyncLayers(contextUri = '*') {
|
|
619
|
+
const layerService = this.injector.get(LayerService);
|
|
620
|
+
return this.layerDB.getAll().pipe(concatMap$1((persistedLayers) => {
|
|
621
|
+
const filteredPersistedLayers = contextUri === '*'
|
|
622
|
+
? persistedLayers
|
|
623
|
+
: persistedLayers.filter((layer) => layer.detailedContextUri === contextUri);
|
|
624
|
+
if (!filteredPersistedLayers.length) {
|
|
625
|
+
return of([]);
|
|
626
|
+
}
|
|
627
|
+
const layerOptions = filteredPersistedLayers.map((persistedLayer) => ({
|
|
628
|
+
...persistedLayer.layerOptions,
|
|
629
|
+
offline: {
|
|
630
|
+
enabled: true,
|
|
631
|
+
contextUri: persistedLayer.detailedContextUri
|
|
632
|
+
},
|
|
633
|
+
sourceOptions: {
|
|
634
|
+
...persistedLayer.sourceOptions,
|
|
635
|
+
url: persistedLayer.sourceOptions.url ?? persistedLayer.layerId
|
|
636
|
+
}
|
|
637
|
+
}));
|
|
638
|
+
return combineLatest(layerOptions.map((options) => layerService.createAsyncLayer(options))).pipe(map$1((layers) => layers.filter(Boolean)));
|
|
639
|
+
}));
|
|
640
|
+
}
|
|
641
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: OfflineLayerRestoreService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
642
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: OfflineLayerRestoreService });
|
|
643
|
+
}
|
|
644
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.19", ngImport: i0, type: OfflineLayerRestoreService, decorators: [{
|
|
645
|
+
type: Injectable
|
|
646
|
+
}] });
|
|
647
|
+
|
|
648
|
+
function provideOffline(...features) {
|
|
649
|
+
const providers = [];
|
|
650
|
+
for (const feature of features) {
|
|
651
|
+
providers.push(...feature.providers);
|
|
652
|
+
}
|
|
653
|
+
return makeEnvironmentProviders(providers);
|
|
654
|
+
}
|
|
655
|
+
function withIndexedDb() {
|
|
656
|
+
return {
|
|
657
|
+
kind: 'IndexedDb',
|
|
658
|
+
providers: [
|
|
659
|
+
provideAppInitializer(indexedDbInitializerFactory),
|
|
660
|
+
provideAppInitializer(configFileToGeoDBInitializerFactory),
|
|
661
|
+
GeoDB,
|
|
662
|
+
LayerDB,
|
|
663
|
+
GeoNetworkService,
|
|
664
|
+
GeoDataSyncService,
|
|
665
|
+
IndexedDbLayerPersistenceService,
|
|
666
|
+
{
|
|
667
|
+
provide: LAYER_PERSISTENCE,
|
|
668
|
+
useExisting: IndexedDbLayerPersistenceService
|
|
669
|
+
},
|
|
670
|
+
OfflineLayerRestoreService,
|
|
671
|
+
{
|
|
672
|
+
provide: OFFLINE_LAYER_RESTORE,
|
|
673
|
+
useExisting: OfflineLayerRestoreService
|
|
674
|
+
},
|
|
675
|
+
IndexedDbVectorLayerExtension,
|
|
676
|
+
{
|
|
677
|
+
provide: VECTOR_LAYER_EXTENSIONS,
|
|
678
|
+
useExisting: IndexedDbVectorLayerExtension,
|
|
679
|
+
multi: true
|
|
680
|
+
}
|
|
681
|
+
]
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
function indexedDbInitializerFactory() {
|
|
685
|
+
inject(GeoDB);
|
|
686
|
+
inject(LayerDB);
|
|
687
|
+
return createIndexedDb();
|
|
688
|
+
}
|
|
689
|
+
async function configFileToGeoDBInitializerFactory() {
|
|
690
|
+
const configService = inject(ConfigService);
|
|
691
|
+
const configFileToGeoDBService = inject(GeoDataSyncService);
|
|
692
|
+
await new Promise((resolve) => {
|
|
693
|
+
configService.isLoaded$.subscribe((loaded) => {
|
|
694
|
+
if (loaded) {
|
|
695
|
+
resolve();
|
|
696
|
+
}
|
|
697
|
+
});
|
|
698
|
+
});
|
|
699
|
+
const url = configService.getConfig('importExport.configFileToGeoDBService');
|
|
700
|
+
if (url) {
|
|
701
|
+
configFileToGeoDBService.load(url);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
/**
|
|
706
|
+
* Generated bundle index. Do not edit.
|
|
707
|
+
*/
|
|
708
|
+
|
|
709
|
+
export { GeoDB, GeoDataSyncService, GeoNetworkService, IndexedDbLayerPersistenceService, IndexedDbVectorLayerExtension, InsertSourceInsertDBEnum, LayerDB, OfflineFeatureKind, createIndexedDb, provideOffline, withIndexedDb };
|
|
710
|
+
//# sourceMappingURL=igo2-geo-offline.mjs.map
|