namazu-ts 0.1.0

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.
Files changed (59) hide show
  1. package/.prettierignore +4 -0
  2. package/.prettierrc +6 -0
  3. package/README.md +13 -0
  4. package/dist/browser/index.css +793 -0
  5. package/dist/browser/index.js +29829 -0
  6. package/dist/client.d.ts +41 -0
  7. package/dist/index.d.ts +5 -0
  8. package/dist/maputils.d.ts +13 -0
  9. package/dist/node/index.css +788 -0
  10. package/dist/node/index.js +42188 -0
  11. package/dist/sismomap.d.ts +21 -0
  12. package/dist/types.d.ts +197 -0
  13. package/dist/utils.d.ts +4 -0
  14. package/docs/.nojekyll +1 -0
  15. package/docs/assets/hierarchy.js +1 -0
  16. package/docs/assets/highlight.css +71 -0
  17. package/docs/assets/icons.js +18 -0
  18. package/docs/assets/icons.svg +1 -0
  19. package/docs/assets/main.js +60 -0
  20. package/docs/assets/navigation.js +1 -0
  21. package/docs/assets/search.js +1 -0
  22. package/docs/assets/style.css +1633 -0
  23. package/docs/classes/Client.html +31 -0
  24. package/docs/classes/LngLatBounds.html +120 -0
  25. package/docs/classes/SismoMap.html +12 -0
  26. package/docs/functions/EventToEventGeoJSON.html +1 -0
  27. package/docs/functions/createMap.html +6 -0
  28. package/docs/hierarchy.html +1 -0
  29. package/docs/index.html +9 -0
  30. package/docs/modules.html +1 -0
  31. package/docs/types/Answer.html +5 -0
  32. package/docs/types/City.html +7 -0
  33. package/docs/types/CleanedFDSNQueryOptions.html +1 -0
  34. package/docs/types/EventFeature.html +5 -0
  35. package/docs/types/EventGeoJSON.html +3 -0
  36. package/docs/types/EventGeoJSONProperties.html +11 -0
  37. package/docs/types/EventGeojsonDescriptionProperty.html +3 -0
  38. package/docs/types/FDSNQueryOptions.html +29 -0
  39. package/docs/types/Form.html +7 -0
  40. package/docs/types/Magnitude.html +16 -0
  41. package/docs/types/Origin.html +27 -0
  42. package/docs/types/Question.html +12 -0
  43. package/docs/types/QuestionChoice.html +5 -0
  44. package/docs/types/QuestionCondition.html +1 -0
  45. package/docs/types/QuestionGroup.html +3 -0
  46. package/docs/types/SisEvent.html +12 -0
  47. package/docs/types/Street.html +4 -0
  48. package/docs/types/Survey.html +5 -0
  49. package/docs/types/Testimony.html +17 -0
  50. package/docs/variables/eventTypes.html +1 -0
  51. package/docs/variables/mapLayers.html +1 -0
  52. package/package.json +54 -0
  53. package/src/client.ts +552 -0
  54. package/src/index.ts +5 -0
  55. package/src/maputils.ts +116 -0
  56. package/src/sismomap.ts +108 -0
  57. package/src/types.ts +237 -0
  58. package/src/utils.ts +20 -0
  59. package/typedoc.json +1 -0
package/src/client.ts ADDED
@@ -0,0 +1,552 @@
1
+ import axios, { AxiosInstance, AxiosRequestConfig } from 'axios';
2
+ import { CleanedFDSNQueryOptions, FDSNQueryOptions } from './types';
3
+ import { ok, err, Result } from 'neverthrow';
4
+ import {
5
+ Form,
6
+ Question,
7
+ Survey,
8
+ City,
9
+ Testimony,
10
+ QuestionChoice,
11
+ Answer,
12
+ EventGeoJSON,
13
+ SisEvent as NamazuEvent,
14
+ } from './types';
15
+
16
+ export class Client {
17
+ // Using a dedicated Axios Instance makes having multiple clients at once possible
18
+ private axiosInstance: AxiosInstance;
19
+
20
+ constructor(baseUrl: string) {
21
+ let cfg = { baseURL: baseUrl };
22
+ this.axiosInstance = axios.create(cfg);
23
+ }
24
+
25
+ setBaseUrl(baseUrl: string) {
26
+ this.axiosInstance.defaults.baseURL = baseUrl;
27
+ }
28
+
29
+ // Can either pass the AxiosRequest for more configurations or a route with a method
30
+ async apiCall<Type>(apiRoute: AxiosRequestConfig): Promise<Type>;
31
+ async apiCall<Type>(apiRoute: string, method?: string): Promise<Type>;
32
+ async apiCall<Type>(
33
+ apiRoute: string | AxiosRequestConfig,
34
+ method: string = 'GET'
35
+ ): Promise<Type> {
36
+ let request: AxiosRequestConfig;
37
+ if (typeof apiRoute == 'string') {
38
+ request = { method: method, url: apiRoute };
39
+ } else {
40
+ request = apiRoute;
41
+ }
42
+ let a: Promise<Type> = this.axiosInstance(request).then((response) => {
43
+ return response.data as Type;
44
+ });
45
+ return a;
46
+ }
47
+
48
+ async fetchForm(id: number): Promise<Form> {
49
+ return this.apiCall<Form>(`/api/v1/surveys/forms/${id}`);
50
+ }
51
+
52
+ getQuestion(form: Form, question_id: number): Question | undefined {
53
+ return form.questions.find((question) => question.id == question_id);
54
+ }
55
+
56
+ getChoice(
57
+ question: Question,
58
+ choice_id: number
59
+ ): QuestionChoice | undefined {
60
+ return question.choices.find((choice) => choice.id == choice_id);
61
+ }
62
+
63
+ async createIndividualTestimony(
64
+ eventPublicID: string,
65
+ email: string
66
+ // naiveFeltTime: Date | null
67
+ ): Promise<Testimony> {
68
+ return this.apiCall<Testimony>({
69
+ url: `/api/v1/testimonies`,
70
+ method: 'POST',
71
+ params: {
72
+ event_publicid: eventPublicID,
73
+ type: 'individual',
74
+ email: email,
75
+ },
76
+ });
77
+ }
78
+
79
+ async createCityTestimony(
80
+ eventPublicID: string,
81
+ cityID: number,
82
+ administrativeCodeType: string,
83
+ administrativeCode: string,
84
+ author: string,
85
+ email: string,
86
+ organization: string
87
+ ): Promise<Testimony> {
88
+ return this.apiCall<Testimony>({
89
+ method: 'post',
90
+ url: `/api/v1/testimonies`,
91
+ params: {
92
+ event_publicid: eventPublicID,
93
+ city_id: cityID,
94
+ administrative_code_type: administrativeCodeType,
95
+ administrative_code: administrativeCode,
96
+ type: 'city',
97
+ author: author,
98
+ email: email,
99
+ organization: organization,
100
+ },
101
+ });
102
+ }
103
+
104
+ async updateAnswer(
105
+ key: string,
106
+ questionID: number,
107
+ choices: number[],
108
+ value: string
109
+ ): Promise<Answer[]> {
110
+ return this.apiCall<Answer[]>({
111
+ method: 'put',
112
+ url: `/api/v1/testimonies/${key}/answers`,
113
+ params: {
114
+ question_id: questionID,
115
+ choices: choices,
116
+ value: value,
117
+ },
118
+ });
119
+ }
120
+
121
+ async updateTestimonyLocation(
122
+ key: string,
123
+ longitude: number,
124
+ latitude: number
125
+ ): Promise<Testimony | void> {
126
+ return this.apiCall<Testimony>({
127
+ method: 'put',
128
+ url: `/api/v1/testimonies/${key}`,
129
+ params: {
130
+ longitude: longitude,
131
+ latitude: latitude,
132
+ },
133
+ });
134
+ }
135
+
136
+ async setTestimonyDone(
137
+ key: string,
138
+ testimonyDone: boolean
139
+ ): Promise<Testimony | void> {
140
+ let evaluationStatus = testimonyDone ? 'final' : 'inprogress';
141
+
142
+ return this.apiCall<Testimony>({
143
+ method: 'put',
144
+ url: `/api/v1/testimonies/${key}`,
145
+ params: {
146
+ user_evaluation_status: evaluationStatus,
147
+ },
148
+ });
149
+ }
150
+
151
+ async fetchEvent(eventPublicID: string): Promise<NamazuEvent> {
152
+ return this.apiCall<NamazuEvent>(`/api/v1/events/${eventPublicID}`);
153
+ }
154
+
155
+ async fetchEvents(startDate: Date, endDate: Date): Promise<EventGeoJSON> {
156
+ return this.apiCall<EventGeoJSON>({
157
+ method: 'get',
158
+ url: '/api/v1/events/',
159
+ params: {
160
+ start_time: startDate.toISOString(),
161
+ end_time: endDate.toISOString(),
162
+ },
163
+ });
164
+ }
165
+
166
+ async fetchSurvey(surveyKey: string): Promise<Survey> {
167
+ return this.apiCall<Survey>({
168
+ method: 'get',
169
+ url: `/api/v1/surveys/cities/${surveyKey}`,
170
+ });
171
+ }
172
+
173
+ async fetchTestimony(key: string): Promise<Testimony> {
174
+ return this.apiCall<Testimony>(`/api/v1/testimonies/${key}`);
175
+ }
176
+
177
+ async fetchAnswers(key: string): Promise<Answer[]> {
178
+ return this.apiCall<Answer[]>(`/api/v1/testimonies/${key}/answers`);
179
+ }
180
+
181
+ async fetchCitiesByMatch(match: string): Promise<City[]> {
182
+ // Axios throws an error on not 2XX code so try/catch works
183
+ try {
184
+ return this.apiCall<City[]>({
185
+ method: 'get',
186
+ url: '/api/v1/cities',
187
+ params: {
188
+ match: match,
189
+ },
190
+ });
191
+ } catch (error) {
192
+ return [];
193
+ }
194
+ }
195
+
196
+ async fetchCitiesByAdministrativeCode(
197
+ administrative_code_type: string,
198
+ administrative_code: string
199
+ ) {
200
+ return this.apiCall<City[]>({
201
+ method: 'get',
202
+ url: '/api/v1/cities',
203
+ params: {
204
+ administrative_code_type: administrative_code_type,
205
+ administrative_code: administrative_code,
206
+ },
207
+ });
208
+ }
209
+
210
+ // The next functions could be out of the object but i put them in so everything about forms
211
+ // is centralized and you just have to import the object once and that's all
212
+
213
+ formatMagnitude(magnitude: number): string {
214
+ return magnitude.toFixed(1);
215
+ }
216
+
217
+ getCurrentChoices(question_id: number, answers: Answer[]) {
218
+ const choices = answers.find(
219
+ (answer) => answer.question_id == question_id
220
+ )?.choices;
221
+
222
+ return choices ? choices : [];
223
+ }
224
+
225
+ getCurrentValue(question_id: number, answers: Answer[]) {
226
+ const answer = answers.find(
227
+ (answer) => answer.question_id == question_id
228
+ );
229
+
230
+ return answer?.value;
231
+ }
232
+
233
+ isConditionsSatisfied(
234
+ question: Question,
235
+ answers: Answer[] | undefined
236
+ ): boolean {
237
+ if (question.conditions.length === 0) {
238
+ return true;
239
+ }
240
+
241
+ if (!answers) {
242
+ return false;
243
+ }
244
+
245
+ const currentChoices = answers.map((answer) => answer.choices).flat();
246
+
247
+ return question.conditions
248
+ .map((condition) =>
249
+ condition.every((choice) => currentChoices.includes(choice))
250
+ )
251
+ .some((conditionsSatisfied) => conditionsSatisfied);
252
+ }
253
+
254
+ getLastAnsweredQuestion(
255
+ form: Form,
256
+ answers: Answer[]
257
+ ): Question | undefined {
258
+ const answeredQuestions = answers.map((answer) => answer.question_id);
259
+
260
+ return form.questions
261
+ .filter((question) => answeredQuestions.includes(question.id))
262
+ .sort((a, b) => (a.order > b.order ? -1 : 1))
263
+ .at(0);
264
+ }
265
+
266
+ getPreviousQuestion(
267
+ form: Form,
268
+ answers: Answer[],
269
+ currentQuestion?: Question
270
+ ): Question | undefined {
271
+ if (currentQuestion) {
272
+ return form.questions
273
+ .filter((question) => question.order < currentQuestion.order)
274
+ .sort((a, b) => (a.order > b.order ? -1 : 1))
275
+ .find((question) =>
276
+ this.isConditionsSatisfied(question, answers)
277
+ );
278
+ }
279
+ }
280
+
281
+ getNextQuestion(
282
+ form: Form,
283
+ answers: Answer[],
284
+ currentQuestion?: Question
285
+ ): Question | undefined {
286
+ let minOrder = 0;
287
+
288
+ if (currentQuestion) {
289
+ minOrder = currentQuestion.order;
290
+ } else {
291
+ const lastAnsweredQuestion = this.getLastAnsweredQuestion(
292
+ form,
293
+ answers
294
+ );
295
+
296
+ if (lastAnsweredQuestion) {
297
+ minOrder = lastAnsweredQuestion.order;
298
+ }
299
+ }
300
+
301
+ return form.questions
302
+ .filter((question) => question.order > minOrder)
303
+ .sort((a, b) => (a.order < b.order ? -1 : 1))
304
+ .find((question) => this.isConditionsSatisfied(question, answers));
305
+ }
306
+
307
+ formatDateFR(dateString: Date): string {
308
+ const date = new Date(dateString);
309
+ return `${date.getDay().toString().padStart(2, '0')}/${date
310
+ .getMonth()
311
+ .toString()
312
+ .padStart(
313
+ 2,
314
+ '0'
315
+ )}/${date.getFullYear().toString().padStart(2, '0')}`;
316
+ }
317
+
318
+ translateEventType(eventType: string): string {
319
+ return eventTypes.get(eventType) || 'Évènement';
320
+ }
321
+
322
+ buildQuery(
323
+ url: string,
324
+ options: CleanedFDSNQueryOptions
325
+ ): AxiosRequestConfig {
326
+ return {
327
+ method: 'get',
328
+ url: url,
329
+ params: options,
330
+ };
331
+ }
332
+
333
+ /**
334
+ * <!> IF YOU WANT TO CAST AS NAMAZU TYPE LATER, SET format:'json'
335
+ */
336
+ cleanQuery(
337
+ options: FDSNQueryOptions
338
+ ): Result<CleanedFDSNQueryOptions, Error> {
339
+ let query: FDSNQueryOptions = {};
340
+
341
+ if (options.eventid !== undefined) {
342
+ query.eventid = options.eventid;
343
+ return ok(query as CleanedFDSNQueryOptions);
344
+ }
345
+
346
+ if (options.starttime !== undefined) {
347
+ query.starttime = options.starttime;
348
+ } else {
349
+ // Default value is a month ago
350
+ let date = new Date();
351
+ date.setDate(date.getDate() - 30);
352
+ query.starttime = date;
353
+ }
354
+
355
+ if (options.endtime !== undefined) {
356
+ query.endtime = options.endtime;
357
+ }
358
+
359
+ if (options.minlatitude !== undefined) {
360
+ if (options.minlatitude <= 90 && options.minlatitude >= -90) {
361
+ query.minlatitude = options.minlatitude;
362
+ } else {
363
+ return err(
364
+ Error('Minimum latitude should be between -90 and 90')
365
+ );
366
+ }
367
+ }
368
+
369
+ if (options.maxlatitude !== undefined) {
370
+ if (options.maxlatitude <= 90 && options.maxlatitude >= -90) {
371
+ query.maxlatitude = options.maxlatitude;
372
+ } else {
373
+ return err(
374
+ Error('Maximum latitude should be between -90 and 90')
375
+ );
376
+ }
377
+ }
378
+
379
+ if (options.minlongitude !== undefined) {
380
+ if (options.minlongitude <= 180 && options.minlongitude >= -180) {
381
+ query.minlongitude = options.minlongitude;
382
+ } else {
383
+ return err(
384
+ Error('Minimum longitude should be between -180 and 180')
385
+ );
386
+ }
387
+ }
388
+
389
+ if (options.maxlongitude !== undefined) {
390
+ if (options.maxlongitude <= 180 && options.maxlongitude >= -180) {
391
+ query.maxlongitude = options.maxlongitude;
392
+ } else {
393
+ return err(
394
+ Error('Maximum longitude should be between -180 and 180')
395
+ );
396
+ }
397
+ }
398
+
399
+ // A bit ugly but one of these parameters can't live without the others
400
+ // so if one exists, i must create the others (if needed)
401
+ if (options.latitude !== undefined) {
402
+ if (options.latitude <= 90 && options.latitude >= -90) {
403
+ query.latitude = options.latitude;
404
+ } else {
405
+ return err(Error('Latitude should be between -90 and 90'));
406
+ }
407
+ }
408
+
409
+ if (options.longitude !== undefined) {
410
+ if (options.longitude <= 180 && options.longitude >= -180) {
411
+ query.longitude = options.longitude;
412
+ } else {
413
+ return err(Error('Longitude should be between -180 and 180'));
414
+ }
415
+ }
416
+ if (options.minradius !== undefined) {
417
+ if (options.minradius >= 0 && options.minradius <= 180) {
418
+ query.minradius = options.minradius;
419
+ } else {
420
+ return err(Error('Min radius should be between 0 and 180'));
421
+ }
422
+ }
423
+ if (options.maxradius !== undefined) {
424
+ if (options.maxradius >= 0 && options.maxradius <= 180) {
425
+ query.maxradius = options.maxradius;
426
+ } else {
427
+ return err(Error('Max radius should be between 0 and 180'));
428
+ }
429
+ }
430
+
431
+ const keys: (keyof FDSNQueryOptions)[] = [
432
+ 'mindepth',
433
+ 'maxdepth',
434
+ 'minmagnitude',
435
+ 'maxmagnitude',
436
+ 'magnitudetype',
437
+ 'eventtype',
438
+ 'includeallorigins',
439
+ 'includeallmagnitudes',
440
+ 'includearrivals',
441
+ 'catalog',
442
+ 'contributor',
443
+ 'updatedafter',
444
+ ];
445
+ keys.forEach((key) => {
446
+ if (options[key] !== undefined) {
447
+ // @ts-expect-error This line is safe as it's guarded above yet typescript errors because
448
+ // it doesn't understand the typeguard
449
+ query[key] = options[key];
450
+ }
451
+ });
452
+
453
+ if (options.limit !== undefined) {
454
+ if (options.limit > 0) {
455
+ query.limit = options.limit;
456
+ } else {
457
+ return err(Error("Limit can't be 0"));
458
+ }
459
+ }
460
+
461
+ if (options.offset !== undefined) {
462
+ if (options.offset > 0) {
463
+ query.offset = options.offset;
464
+ } else {
465
+ return err(Error("Offset can't be 0"));
466
+ }
467
+ }
468
+
469
+ let values = ['time', 'time-asc', 'magnitude', 'magnitude-asc'];
470
+ if (options.orderby !== undefined) {
471
+ if (values.includes(options.orderby)) {
472
+ query.orderby = options.orderby;
473
+ } else {
474
+ return err(
475
+ Error(
476
+ 'Orderby must be time, time-asc, magnitude or magnitude-asc'
477
+ )
478
+ );
479
+ }
480
+ }
481
+
482
+ values = ['xml', 'text', 'json'];
483
+ if (options.format !== undefined) {
484
+ if (values.includes(options.format)) {
485
+ query.format = options.format;
486
+ } else {
487
+ return err(Error('Format must be xml, text or json'));
488
+ }
489
+ }
490
+
491
+ values = ['204', '404'];
492
+ if (options.nodata !== undefined) {
493
+ if (values.includes(options.nodata)) {
494
+ query.nodata = options.nodata;
495
+ } else {
496
+ return err(Error('Nodata must be either 204 or 404'));
497
+ }
498
+ }
499
+
500
+ return ok(query as CleanedFDSNQueryOptions);
501
+ }
502
+ }
503
+
504
+ // Export in case people want official translations
505
+ export const eventTypes = new Map<string, string>([
506
+ ['accidental explosion', 'explosion accidentelle'],
507
+ ['acoustic noise', 'bruit acoustique'],
508
+ ['anthropogenic event', 'évènement anthropique'],
509
+ ['avalanche', 'avalanche'],
510
+ ['blasting levee', 'digue de dynamitage'],
511
+ ['boat crash', 'accident de bateau'],
512
+ ['building collapse', 'effondrement de bâtiment'],
513
+ ['cavity collapse', 'effondrement de la cavité'],
514
+ ['chemical explosion', 'explosion chimique'],
515
+ ['collapse', 'effondrement'],
516
+ ['controlled explosion', 'explosion contrôlée'],
517
+ ['crash', 'accident'],
518
+ ['debris avalanche', 'avalanche de débris'],
519
+ ['earthquake', 'tremblement de terre'],
520
+ ['event', 'évènement'],
521
+ ['experimental explosion', 'explosion expérimentale'],
522
+ ['explosion', 'explosion'],
523
+ ['fluid extraction', 'extraction de fluide'],
524
+ ['fluid injection', 'injection de fluide'],
525
+ ['hydroacoustic event', 'évènement hydroacoustique'],
526
+ ['ice quake', 'tremblement de glace'],
527
+ ['induced', 'induit'],
528
+ ['industrial explosion', 'explosion industrielle'],
529
+ ['landslide', 'glissement de terrain'],
530
+ ['meteorite', 'météorite'],
531
+ ['mine collapse', 'effondrement de mine'],
532
+ ['mining explosion', 'explosion de mine'],
533
+ ['not existing', 'inexistant'],
534
+ ['not locatable', 'non localisable'],
535
+ ['not reported', 'non reporté'],
536
+ ['nuclear explosion', 'explosion nucléaire'],
537
+ ['other event', 'autre événement'],
538
+ ['outside of network interest', "en dehors de la zone d'intérêt"],
539
+ ['plane crash', "crash d'avion"],
540
+ ['quarry blast', 'tir de carrière'],
541
+ ['reservoir loading', 'chargement de réservoir'],
542
+ ['road cut', 'coupe de route'],
543
+ ['rock burst', 'éclat de roche'],
544
+ ['rockslide', 'éboulement'],
545
+ ['slide', 'glissement'],
546
+ ['snow avalanche', 'avalanche de neige'],
547
+ ['sonic blast', 'effet de souffle'],
548
+ ['sonic boom', 'détonation supersonique'],
549
+ ['thunder', 'tonnerre'],
550
+ ['train crash', 'crash de train'],
551
+ ['volcanic eruption', 'éruption volcanique'],
552
+ ]);
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export { LngLatBounds } from 'maplibre-gl';
2
+ export * from './maputils';
3
+ export * from './client';
4
+ export * from './sismomap';
5
+ export * from './types';
@@ -0,0 +1,116 @@
1
+ import maplibregl from 'maplibre-gl';
2
+ import 'maplibre-gl/dist/maplibre-gl.css';
3
+ import geojson from 'geojson';
4
+ import { SismoMap } from './sismomap';
5
+ import {
6
+ SisEvent,
7
+ EventFeature,
8
+ EventGeoJSON,
9
+ EventGeojsonDescriptionProperty,
10
+ mapLayers,
11
+ } from './types';
12
+ import { isEventGeoJSONProperties } from './utils';
13
+
14
+ /**
15
+ * Map creating function,
16
+ * @param name Name of the map, doesn't influence the behaviour
17
+ * @param containerID ID of the container that the map will be in
18
+ * @param bounds Optional bounds of the view when creating the map if absent, defaults to 0,0
19
+ * @return Promise<SismoMap> Promise that will be resolved when the map is created (bound zoom might not be over, but it's not a problem, zooms adjusts in real time to other functions)
20
+ */
21
+ export async function createMap(
22
+ name: string,
23
+ containerID: string,
24
+ lang: string,
25
+ bounds?: maplibregl.LngLatBounds
26
+ ): Promise<SismoMap> {
27
+ const style: any = await fetch(
28
+ 'https://static.franceseisme.fr/pmtiles-style.json'
29
+ ).then((r) => r.json());
30
+
31
+ style.projection = { type: 'globe' };
32
+
33
+ const map = new maplibregl.Map({
34
+ container: containerID,
35
+ style: style,
36
+ center: [0, 0],
37
+ zoom: 1, // Low value at the start so we get a "zoom in" animation when loading
38
+ attributionControl: false,
39
+ });
40
+
41
+ let descPopup = new maplibregl.Popup({
42
+ closeButton: false,
43
+ closeOnClick: false,
44
+ maxWidth: '200px',
45
+ });
46
+ let smap = new SismoMap(name, map, descPopup);
47
+
48
+ // We use a promise so that people won't use the map before it's fully
49
+ return new Promise<SismoMap>((resolve) => {
50
+ map.once('load', () => {
51
+ // We use that to have a flat map when zooming
52
+ // 5 is the value chosen because it covers all the zones of renass.unistra.franceseisme
53
+ // feel free to change to your liking if needed
54
+ map.setProjection({
55
+ type: ['step', ['zoom'], 'vertical-perspective', 5, 'mercator'],
56
+ });
57
+
58
+ if (typeof bounds != 'undefined') {
59
+ map.fitBounds(bounds);
60
+ }
61
+
62
+ mapLayers.forEach((title: string) => {
63
+ if (title != 'stations-layer') {
64
+ smap.map.on('mouseenter', title, (e) => {
65
+ smap.map.getCanvas().style.cursor = 'pointer';
66
+ if (e.features == undefined) return;
67
+ if (
68
+ !isEventGeoJSONProperties(
69
+ e.features[0].properties as any
70
+ )
71
+ )
72
+ return;
73
+ let text = JSON.parse(
74
+ e.features[0].properties.description
75
+ );
76
+ console.log(text);
77
+
78
+ // Pretty ugly but as we know, it'll always be a point, we can do it like this
79
+ const feature = e
80
+ .features![0] as geojson.Feature<geojson.Point>;
81
+ const [lng, lat] = feature.geometry.coordinates;
82
+
83
+ descPopup
84
+ .setLngLat([lng, lat])
85
+ .setHTML(text[lang])
86
+ .addTo(smap.map);
87
+ });
88
+ smap.map.on('mouseleave', title, () => {
89
+ descPopup.remove();
90
+ smap.map.getCanvas().style.cursor = '';
91
+ });
92
+ }
93
+ });
94
+
95
+ resolve(smap);
96
+ });
97
+ });
98
+ }
99
+
100
+ export function EventToEventGeoJSON(e: SisEvent): EventGeoJSON {
101
+ let evGeoJson: EventGeoJSON = { type: 'FeatureCollection', features: [] };
102
+
103
+ const lng: number = e.preferredOrigin.longitude;
104
+ const lat: number = e.preferredOrigin.latitude;
105
+
106
+ let feature: EventFeature = {
107
+ id: e.publicid,
108
+ properties: e,
109
+ geometry: { coordinates: [lng, lat], type: 'Point' },
110
+ type: 'Feature',
111
+ };
112
+
113
+ evGeoJson.features.push(feature);
114
+
115
+ return evGeoJson;
116
+ }