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.
- package/.prettierignore +4 -0
- package/.prettierrc +6 -0
- package/README.md +13 -0
- package/dist/browser/index.css +793 -0
- package/dist/browser/index.js +29829 -0
- package/dist/client.d.ts +41 -0
- package/dist/index.d.ts +5 -0
- package/dist/maputils.d.ts +13 -0
- package/dist/node/index.css +788 -0
- package/dist/node/index.js +42188 -0
- package/dist/sismomap.d.ts +21 -0
- package/dist/types.d.ts +197 -0
- package/dist/utils.d.ts +4 -0
- package/docs/.nojekyll +1 -0
- package/docs/assets/hierarchy.js +1 -0
- package/docs/assets/highlight.css +71 -0
- package/docs/assets/icons.js +18 -0
- package/docs/assets/icons.svg +1 -0
- package/docs/assets/main.js +60 -0
- package/docs/assets/navigation.js +1 -0
- package/docs/assets/search.js +1 -0
- package/docs/assets/style.css +1633 -0
- package/docs/classes/Client.html +31 -0
- package/docs/classes/LngLatBounds.html +120 -0
- package/docs/classes/SismoMap.html +12 -0
- package/docs/functions/EventToEventGeoJSON.html +1 -0
- package/docs/functions/createMap.html +6 -0
- package/docs/hierarchy.html +1 -0
- package/docs/index.html +9 -0
- package/docs/modules.html +1 -0
- package/docs/types/Answer.html +5 -0
- package/docs/types/City.html +7 -0
- package/docs/types/CleanedFDSNQueryOptions.html +1 -0
- package/docs/types/EventFeature.html +5 -0
- package/docs/types/EventGeoJSON.html +3 -0
- package/docs/types/EventGeoJSONProperties.html +11 -0
- package/docs/types/EventGeojsonDescriptionProperty.html +3 -0
- package/docs/types/FDSNQueryOptions.html +29 -0
- package/docs/types/Form.html +7 -0
- package/docs/types/Magnitude.html +16 -0
- package/docs/types/Origin.html +27 -0
- package/docs/types/Question.html +12 -0
- package/docs/types/QuestionChoice.html +5 -0
- package/docs/types/QuestionCondition.html +1 -0
- package/docs/types/QuestionGroup.html +3 -0
- package/docs/types/SisEvent.html +12 -0
- package/docs/types/Street.html +4 -0
- package/docs/types/Survey.html +5 -0
- package/docs/types/Testimony.html +17 -0
- package/docs/variables/eventTypes.html +1 -0
- package/docs/variables/mapLayers.html +1 -0
- package/package.json +54 -0
- package/src/client.ts +552 -0
- package/src/index.ts +5 -0
- package/src/maputils.ts +116 -0
- package/src/sismomap.ts +108 -0
- package/src/types.ts +237 -0
- package/src/utils.ts +20 -0
- 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
package/src/maputils.ts
ADDED
|
@@ -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
|
+
}
|