@mapcreator/api 0.0.0-saga.1 → 0.0.0-wms.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.
@@ -1,105 +1,359 @@
1
- import { type ApiError, type ApiSuccess, getSearchParams, request } from '../utils.js';
2
- import type { Polygon } from 'geojson';
1
+ import {
2
+ APIMeta,
3
+ type ApiCommonData,
4
+ type ApiError,
5
+ type ApiSuccess,
6
+ type Flatten,
7
+ defaultListHeader,
8
+ ensureArray,
9
+ getSearchParams,
10
+ request,
11
+ toAppType,
12
+ } from '../utils.js';
13
+ import type { FeatureCollection, Polygon } from 'geojson';
14
+ import type { SnakeCase } from 'type-fest';
3
15
 
4
- export type ApiSearchPoint = {
5
- lat: number;
6
- lng: number;
16
+ type FieldName = 'id' | 'name' | 'description' | 'previewJson' | 'previewPath';
17
+
18
+ const processGroupData = (group: GroupChoropleth): GroupChoropleth => ({
19
+ ...group,
20
+ ...group.nativeParentTranslation && { nativeParentTranslation: toAppType(group.nativeParentTranslation) },
21
+ ...group.nativeRelationTranslation && { nativeRelationTranslation: toAppType(group.nativeRelationTranslation) },
22
+ ...group.englishParentTranslation && { englishParentTranslation: toAppType(group.englishParentTranslation) },
23
+ ...group.englishRelationTranslation && { englishRelationTranslation: toAppType(group.englishRelationTranslation) },
24
+ ...group.parentTranslations && { parentTranslations: ensureArray(group.parentTranslations).map(toAppType) },
25
+ ...group.relationTranslations && { relationTranslations: ensureArray(group.relationTranslations).map(toAppType) },
26
+ });
27
+
28
+ const processPolygonData = (polygon: PolygonChoropleth): PolygonChoropleth => ({
29
+ ...polygon,
30
+ ...polygon.parentGroups && { parentGroups: ensureArray(polygon.parentGroups).map(toAppType).map(processGroupData) },
31
+ ...polygon.nativeTranslation && { nativeTranslation: toAppType(polygon.nativeTranslation) },
32
+ ...polygon.englishTranslation && { englishTranslation: toAppType(polygon.englishTranslation) },
33
+ ...polygon.translations && { translations: ensureArray(polygon.translations).map(toAppType) },
34
+ });
35
+
36
+ export type PartialVectorChoropleth = Pick<VectorChoropleth, FieldName>;
37
+
38
+ export type VectorChoropleth = {
39
+ id: number;
40
+ name: string;
41
+ description: string;
42
+ previewPath: string | null;
43
+ previewJson: FeatureCollection | null;
44
+
45
+ vectorSetUrl: string;
46
+ sourceLayerName: string;
47
+
48
+ lngMin: number;
49
+ lngMax: number;
50
+ latMin: number;
51
+ latMax: number;
52
+
53
+ keys: string[];
54
+ properties: Array<Record<string, unknown>>;
7
55
  };
8
56
 
9
- export type ApiSearchBounds = {
57
+ export type ApiVectorChoropleth = {
58
+ data: {
59
+ id: number;
60
+ name: string;
61
+ description: string;
62
+ preview_path: string | null;
63
+ preview_json: FeatureCollection | null;
64
+
65
+ vector_set_url: string;
66
+ source_layer_name: string;
67
+
68
+ lng_min: number;
69
+ lng_max: number;
70
+ lat_min: number;
71
+ lat_max: number;
72
+
73
+ keys: string[];
74
+ properties: Array<Record<string, unknown>>;
75
+ } & ApiCommonData;
76
+ } & Omit<ApiSuccess, 'data'> | ApiError;
77
+
78
+ export type ApiVectorChoroplethData = Flatten<Exclude<ApiVectorChoropleth, ApiError>['data']>;
79
+
80
+ export async function listVectorChoropleths(name: string): Promise<PartialVectorChoropleth[]> {
81
+ const pathname = `/v1/choropleths/vector`;
82
+ const query = getSearchParams({ search: { name } });
83
+ const path = `${pathname}?${query}`;
84
+
85
+ // Only request first 50 results
86
+ const headers = { ...defaultListHeader };
87
+ const options = { withMeta: true };
88
+
89
+ type ApiVectorChoroplethArray = {
90
+ data: Array<Pick<ApiVectorChoroplethData, SnakeCase<FieldName>>>;
91
+ } & Omit<ApiSuccess, 'data'> | ApiError;
92
+
93
+ return request<ApiVectorChoroplethArray, PartialVectorChoropleth>(path, null, headers, options)
94
+ .catch((error: Error) => {
95
+ if (error instanceof APIMeta) {
96
+ return (error as APIMeta<ApiVectorChoroplethArray, PartialVectorChoropleth>).data;
97
+ }
98
+
99
+ throw error;
100
+ });
101
+ }
102
+
103
+ export async function getVectorChoropleth(choroplethId: number): Promise<VectorChoropleth> {
104
+ const path = `/v1/choropleths/vector/${choroplethId}`;
105
+
106
+ return request<ApiVectorChoropleth, VectorChoropleth>(path);
107
+ }
108
+
109
+ export type SearchBounds = {
10
110
  min_lat: number;
11
111
  min_lng: number;
12
112
  max_lat: number;
13
113
  max_lng: number;
14
114
  };
15
115
 
16
- type ApiSingleOrGroupedAreaArray =
17
- | ({ data: ApiSingleOrGroupedArea[] } & Omit<ApiSuccess, 'data'>)
18
- | ApiError;
19
-
20
- type ApiSingleOrGroupedArea = {
21
- id: number;
22
- title: string;
23
- subtitle: string;
24
- svg_preview: string;
25
- bounding_box: string;
26
- is_group: boolean;
27
- vector_source: string | null;
28
- source_layer: string | null;
29
- feature_id: number | null;
30
- properties: Record<string, string> | null;
31
- };
32
-
33
- type SingleOrGroupedAreaBase = {
116
+ export type PolygonChoropleth = {
117
+ sml: string;
34
118
  id: number;
35
- title: string;
36
- subtitle: string;
119
+ vectorSource: string;
120
+ sourceLayer: string;
121
+ featureId: number;
37
122
  svgPreview: string;
123
+ previewPath: string;
124
+ allowSingle: boolean;
125
+ properties: Record<string, unknown>;
38
126
  boundingBox: Polygon;
127
+ restrictedOwnership: number[] | null;
128
+ linkId: number | null;
129
+ cloning: boolean;
130
+ nativeTranslation: {
131
+ polygonId: number;
132
+ name: string;
133
+ };
134
+ englishTranslation?: {
135
+ polygonId: number;
136
+ name: string;
137
+ };
138
+ translations?: Array<{
139
+ polygonId: number;
140
+ language: string;
141
+ name: string;
142
+ }>;
143
+ parentGroups: GroupChoropleth[];
39
144
  };
40
145
 
41
- type GroupedArea = SingleOrGroupedAreaBase & {
42
- isGroup: true;
43
- };
146
+ export type ApiPolygonChoropleth = {
147
+ data: {
148
+ sml: string;
149
+ id: number;
150
+ vector_source: string;
151
+ source_layer: string;
152
+ feature_id: number;
153
+ svg_preview: string;
154
+ preview_path: string;
155
+ allow_single: boolean;
156
+ properties: Record<string, unknown>;
157
+ bounding_box: Polygon;
158
+ restricted_ownership: number[] | null;
159
+ link_id: number | null;
160
+ cloning: boolean;
161
+ native_translation: {
162
+ polygon_id: number;
163
+ name: string;
164
+ };
165
+ english_translation?: {
166
+ polygon_id: number;
167
+ name: string;
168
+ };
169
+ translations?: Array<{
170
+ polygon_id: number;
171
+ language: string;
172
+ name: string;
173
+ }>;
174
+ parent_groups: ApiGroupChoroplethData[];
175
+ } & ApiCommonData;
176
+ } & Omit<ApiSuccess, 'data'> | ApiError;
44
177
 
45
- type SingleArea = SingleOrGroupedAreaBase & {
46
- isGroup: false;
47
- vectorSource: string;
48
- sourceLayer: string;
49
- featureId: number;
50
- properties: Record<string, string>;
51
- };
178
+ export type ApiPolygonChoroplethData = Flatten<Exclude<ApiPolygonChoropleth, ApiError>['data']>;
52
179
 
53
- export type SingleOrGroupedArea = SingleArea | GroupedArea;
54
-
55
- export async function searchSingleOrGroupedAreas(
56
- language: string,
57
- searchBounds: ApiSearchBounds,
58
- query?: string,
59
- searchPoint?: ApiSearchPoint,
60
- ): Promise<SingleOrGroupedArea[]> {
61
- return request<ApiSingleOrGroupedAreaArray, SingleOrGroupedArea>(
62
- `/v1/choropleth/polygons/search?${getSearchParams({
63
- language,
180
+ export async function listPolygonChoropleths(
181
+ name: string,
182
+ languages: string[],
183
+ searchBounds?: SearchBounds,
184
+ withParentGroups = false,
185
+ ): Promise<PolygonChoropleth[]> {
186
+ const pathname = `/v1/choropleth/polygons`;
187
+ const query = getSearchParams(
188
+ {
189
+ search: { name, allow_single: true },
190
+ with_parent_groups: withParentGroups,
191
+ languages,
64
192
  ...searchBounds,
65
- ...(query !== '' && { query }),
66
- ...(searchPoint && { point: searchPoint }),
67
- })}`,
68
- ).then(result => {
69
- result.forEach(
70
- elem => (elem.boundingBox = JSON.parse(elem.boundingBox as unknown as string) as Polygon),
71
- );
72
-
73
- return result;
74
- });
75
- }
193
+ },
194
+ );
195
+ const path = `${pathname}?${query}`;
76
196
 
77
- type ApiGroupedAreaChildArray =
78
- | ({
79
- data: ApiGroupedAreaChild[];
80
- } & Omit<ApiSuccess, 'data'>)
81
- | ApiError;
197
+ // Only request first 50 results
198
+ const headers = { ...defaultListHeader };
199
+ const options = { withMeta: true };
82
200
 
83
- type ApiGroupedAreaChild = {
84
- id: number;
85
- title: string;
86
- vector_source: string;
87
- source_layer: string;
88
- feature_id: number;
89
- properties: Record<string, string>;
90
- };
201
+ type ApiPolygonChoroplethArray = {
202
+ data: ApiPolygonChoroplethData[];
203
+ } & Omit<ApiSuccess, 'data'> | ApiError;
91
204
 
92
- export type GroupedAreaChild = {
205
+ return request<ApiPolygonChoroplethArray, PolygonChoropleth>(path, null, headers, options)
206
+ .catch((error: Error) => {
207
+ if (error instanceof APIMeta) {
208
+ return (error as APIMeta<ApiPolygonChoroplethArray, PolygonChoropleth>).data;
209
+ }
210
+
211
+ throw error;
212
+ })
213
+ .then(result => result.map(processPolygonData));
214
+ }
215
+
216
+ export type GroupChoropleth = {
93
217
  id: number;
94
- title: string;
95
- vectorSource: string;
96
- sourceLayer: string;
97
- featureId: number;
98
- properties: Record<string, string>;
218
+ polygonId: number;
219
+ relationId: number;
220
+ childrenCount: number;
221
+ sml?: string;
222
+ svgPreview: string;
223
+ previewPath: string;
224
+ uniqueProperties: string[];
225
+ boundingBox: Polygon;
226
+ partialProperties: string[];
227
+ restrictedOwnership: number[] | null;
228
+ linkId: number | null;
229
+ cloning: boolean;
230
+ laravelThroughKey?: number;
231
+ nativeParentTranslation: {
232
+ polygonId: number;
233
+ name: string;
234
+ laravelThroughKey: number;
235
+ };
236
+ nativeRelationTranslation: {
237
+ relationId: number;
238
+ name: string;
239
+ laravelThroughKey: number;
240
+ };
241
+ englishParentTranslation?: {
242
+ polygonId: number;
243
+ name: string;
244
+ laravelThroughKey: number;
245
+ };
246
+ englishRelationTranslation?: {
247
+ relationId: number;
248
+ name: string;
249
+ laravelThroughKey: number;
250
+ };
251
+ parentTranslations?: Array<{
252
+ polygonId: number;
253
+ name: string;
254
+ language: string;
255
+ laravelThroughKey: number;
256
+ }>;
257
+ relationTranslations?: Array<{
258
+ relationId: number;
259
+ name: string;
260
+ language: string;
261
+ laravelThroughKey: number;
262
+ }>;
99
263
  };
100
264
 
101
- export async function groupedAreaChildren(groupId: number, language: string): Promise<GroupedAreaChild[]> {
102
- return request<ApiGroupedAreaChildArray, GroupedAreaChild>(
103
- `/v1/choropleth/groups/${groupId}/children-optimized?${getSearchParams({ language })}`,
104
- );
265
+ export type ApiGroupChoropleth = {
266
+ data: {
267
+ id: number;
268
+ polygon_id: number;
269
+ relation_id: number;
270
+ children_count: number;
271
+ sml?: number;
272
+ svg_preview: string;
273
+ preview_path: string;
274
+ unique_properties: string[];
275
+ bounding_box: Polygon;
276
+ partial_properties: string[];
277
+ restricted_ownership: number[] | null;
278
+ link_id: number | null;
279
+ cloning: boolean;
280
+ laravel_through_key?: number;
281
+ native_parent_translation: {
282
+ polygon_id: number;
283
+ name: string;
284
+ laravel_through_key: number;
285
+ };
286
+ native_relation_translation: {
287
+ relation_id: number;
288
+ name: string;
289
+ laravel_through_key: number;
290
+ };
291
+ english_parent_translation?: {
292
+ polygon_id: number;
293
+ name: string;
294
+ laravel_through_key: number;
295
+ };
296
+ english_relation_translation?: {
297
+ relation_id: number;
298
+ name: string;
299
+ laravel_through_key: number;
300
+ };
301
+ parent_translations?: Array<{
302
+ polygon_id: number;
303
+ name: string;
304
+ language: string;
305
+ laravel_through_key: number;
306
+ }>;
307
+ relation_translations?: Array<{
308
+ relation_id: number;
309
+ name: string;
310
+ language: string;
311
+ laravel_through_key: number;
312
+ }>;
313
+ } & ApiCommonData;
314
+ } & Omit<ApiSuccess, 'data'> | ApiError;
315
+
316
+ export type ApiGroupChoroplethData = Flatten<Exclude<ApiGroupChoropleth, ApiError>['data']>;
317
+
318
+ export async function listGroupChoropleths(
319
+ name: string,
320
+ languages: string[],
321
+ searchBounds?: SearchBounds,
322
+ ): Promise<GroupChoropleth[]> {
323
+ const pathname = `/v1/choropleth/groups`;
324
+ const query = getSearchParams({ search: { name }, languages, ...searchBounds });
325
+ const path = `${pathname}?${query}`;
326
+
327
+ // Only request first 50 results
328
+ const headers = { ...defaultListHeader };
329
+ const options = { withMeta: true };
330
+
331
+ type ApiGroupChoroplethArray = {
332
+ data: ApiGroupChoroplethData[];
333
+ } & Omit<ApiSuccess, 'data'> | ApiError;
334
+
335
+ return request<ApiGroupChoroplethArray, GroupChoropleth>(path, null, headers, options)
336
+ .catch((error: Error) => {
337
+ if (error instanceof APIMeta) {
338
+ return (error as APIMeta<ApiGroupChoroplethArray, GroupChoropleth>).data;
339
+ }
340
+
341
+ throw error;
342
+ })
343
+ .then(result => result.map(processGroupData));
344
+ }
345
+
346
+ export async function listChildrenGroupChoropleths(
347
+ groupId: number,
348
+ languages: string[],
349
+ ): Promise<PolygonChoropleth[]> {
350
+ const pathname = `/v1/choropleth/groups/${groupId}/children`;
351
+ const query = getSearchParams({ languages });
352
+ const path = `${pathname}?${query}`;
353
+
354
+ type ApiPolygonChoroplethArray = {
355
+ data: ApiPolygonChoroplethData[];
356
+ } & Omit<ApiSuccess, 'data'> | ApiError;
357
+
358
+ return request<ApiPolygonChoroplethArray, PolygonChoropleth>(path).then(polygons => polygons.map(processPolygonData));
105
359
  }
@@ -0,0 +1,96 @@
1
+ import { apiHost, authenticate, token } from '../oauth.js';
2
+ import {
3
+ APIError,
4
+ type ApiCommonData,
5
+ type ApiError,
6
+ type ApiSuccess,
7
+ type Flatten,
8
+ HTTPError,
9
+ NetworkError,
10
+ request,
11
+ } from '../utils.js';
12
+
13
+ export type InsetMap = {
14
+ id: number;
15
+ description: string;
16
+ jsonFilename: string;
17
+ latMin: number;
18
+ lngMin: number;
19
+ latMax: number;
20
+ lngMax: number;
21
+ };
22
+
23
+ export type ApiInsetMap = {
24
+ data: {
25
+ id: number;
26
+ description: string;
27
+ json_filename: string;
28
+ lat_min: number;
29
+ lng_min: number;
30
+ lat_max: number;
31
+ lng_max: number;
32
+ } & ApiCommonData;
33
+ } & Omit<ApiSuccess, 'data'> | ApiError;
34
+
35
+ export type ApiInsetMapData = Flatten<Exclude<ApiInsetMap, ApiError>['data']>;
36
+
37
+ type Boundary = {
38
+ topLeft: {
39
+ lat: number;
40
+ lng: number;
41
+ };
42
+ bottomRight: {
43
+ lat: number;
44
+ lng: number;
45
+ };
46
+ };
47
+
48
+ export async function listInsetMaps(boundary: Boundary): Promise<InsetMap[]> {
49
+ const path = `/v1/inset-maps/for-boundary`;
50
+
51
+ type ApiInsetMapArray = {
52
+ data: ApiInsetMapData[];
53
+ } & Omit<ApiSuccess, 'data'> | ApiError;
54
+
55
+ return request<ApiInsetMapArray, InsetMap>(path, boundary);
56
+ }
57
+
58
+ export async function getInsetMap(insetMapId: number): Promise<InsetMap> {
59
+ const path = `/v1/inset-maps/${insetMapId}`;
60
+
61
+ return request<ApiInsetMap, InsetMap>(path);
62
+ }
63
+
64
+ export async function getInsetMapTopoJson<TopoJSON>(insetMapId: number): Promise<TopoJSON> {
65
+ const href = `${apiHost}/v1/inset-maps/${insetMapId}/json`;
66
+ const headers = { Accept: 'application/json', ...(token ? { Authorization: token.toString() } : null) };
67
+ const response = await fetch(href, { headers }).catch((error: Error) => {
68
+ throw new NetworkError(error?.message ?? error);
69
+ });
70
+
71
+ if (response.ok) {
72
+ return response.json().catch(() => {
73
+ throw new APIError({
74
+ success: false,
75
+ error: { type: 'SyntaxError', message: 'Malformed JSON response' },
76
+ });
77
+ }) as Promise<TopoJSON>;
78
+ } else {
79
+ // eslint-disable-next-line default-case
80
+ switch (response.status) {
81
+ case 401:
82
+ await authenticate();
83
+ break; // NO-OP
84
+ case 403:
85
+ case 404:
86
+ case 406:
87
+ case 429:
88
+ throw new APIError({
89
+ success: false,
90
+ error: { type: 'HttpException', message: response.statusText },
91
+ });
92
+ }
93
+
94
+ throw new HTTPError(response);
95
+ }
96
+ }
@@ -45,7 +45,8 @@ export type CustomFeature =
45
45
  | 'hosted_svg_output'
46
46
  | 'hosted_png_output'
47
47
  | 'hosted_jpg_output'
48
- | 'developer_menu';
48
+ | 'developer_menu'
49
+ | 'external_sources';
49
50
 
50
51
  export interface Resources {
51
52
  organisation: Organisation;
package/src/index.ts CHANGED
@@ -6,6 +6,7 @@ export * from './api/feature.js';
6
6
  export * from './api/font.js';
7
7
  export * from './api/fontFamily.js';
8
8
  export * from './api/highlight.js';
9
+ export * from './api/insetMap.js';
9
10
  export * from './api/job.js';
10
11
  export * from './api/jobResult.js';
11
12
  export * from './api/jobRevision.js';