@globalfishingwatch/skills 0.0.1

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 (47) hide show
  1. package/README.md +89 -0
  2. package/dist/decode-url/SKILL.md +51 -0
  3. package/dist/decode-url/index.js +20339 -0
  4. package/dist/decode-url/scripts/decode-url.mjs +18 -0
  5. package/dist/decode-url/scripts/register-gfw-resolver.mjs +24 -0
  6. package/dist/encode-url/MAINTENANCE.md +53 -0
  7. package/dist/encode-url/SKILL.md +135 -0
  8. package/dist/encode-url/index.js +20211 -0
  9. package/dist/encode-url/references/areas.json +1401 -0
  10. package/dist/encode-url/references/examples-conversations.md +113 -0
  11. package/dist/encode-url/references/examples.md +234 -0
  12. package/dist/encode-url/references/filters.md +67 -0
  13. package/dist/encode-url/references/highlighted-workspaces.md +65 -0
  14. package/dist/encode-url/references/layers.md +96 -0
  15. package/dist/encode-url/references/ports.json +27507 -0
  16. package/dist/encode-url/references/query-params.md +114 -0
  17. package/dist/encode-url/references/routes.md +24 -0
  18. package/dist/encode-url/scripts/encode-url.mjs +26 -0
  19. package/dist/encode-url/scripts/register-gfw-resolver.mjs +24 -0
  20. package/dist/index.js +20514 -0
  21. package/dist/package.json +35 -0
  22. package/package.json +35 -0
  23. package/src/decode-url/SKILL.md +51 -0
  24. package/src/decode-url/decode.ts +87 -0
  25. package/src/decode-url/index.ts +4 -0
  26. package/src/decode-url/scripts/decode-url.mjs +18 -0
  27. package/src/decode-url/scripts/register-gfw-resolver.mjs +24 -0
  28. package/src/decode-url/workspaces.ts +34 -0
  29. package/src/encode-url/MAINTENANCE.md +53 -0
  30. package/src/encode-url/SKILL.md +135 -0
  31. package/src/encode-url/config.ts +8 -0
  32. package/src/encode-url/dictionary.ts +305 -0
  33. package/src/encode-url/encode.ts +79 -0
  34. package/src/encode-url/index.ts +4 -0
  35. package/src/encode-url/references/areas.json +1401 -0
  36. package/src/encode-url/references/examples-conversations.md +113 -0
  37. package/src/encode-url/references/examples.md +234 -0
  38. package/src/encode-url/references/filters.md +67 -0
  39. package/src/encode-url/references/highlighted-workspaces.md +65 -0
  40. package/src/encode-url/references/layers.md +96 -0
  41. package/src/encode-url/references/ports.json +27507 -0
  42. package/src/encode-url/references/query-params.md +114 -0
  43. package/src/encode-url/references/routes.md +24 -0
  44. package/src/encode-url/routes.ts +166 -0
  45. package/src/encode-url/scripts/encode-url.mjs +26 -0
  46. package/src/encode-url/scripts/register-gfw-resolver.mjs +24 -0
  47. package/src/index.ts +6 -0
@@ -0,0 +1,305 @@
1
+ import * as WORKSPACE_CONFIG from '@fishing-map/config'
2
+
3
+ import { VESSEL_DATAVIEW_INSTANCE_PREFIX } from '@globalfishingwatch/dataviews-client'
4
+
5
+ export type LayerCategory = 'activity' | 'detections' | 'events' | 'environment' | 'context'
6
+
7
+ export type LayerInfo = {
8
+ name: string
9
+ category: LayerCategory
10
+ /** Backend dataview slug, filled automatically by the encoder for layer-library instances */
11
+ dataviewId?: string
12
+ }
13
+
14
+ /**
15
+ * Dataview instance ids used by the fishing-map default workspace and layer library.
16
+ * Ids and slugs come from @fishing-map/config (single source of truth with the app)
17
+ */
18
+ export const LAYERS_DICTIONARY: Record<string, LayerInfo> = {
19
+ // Activity
20
+ [WORKSPACE_CONFIG.AIS_DATAVIEW_INSTANCE_ID]: {
21
+ name: 'Apparent fishing effort (AIS)',
22
+ category: 'activity',
23
+ dataviewId: WORKSPACE_CONFIG.FISHING_DATAVIEW_SLUG_AIS,
24
+ },
25
+ 'fishing-effort-ais': {
26
+ name: 'Apparent fishing effort (AIS)',
27
+ category: 'activity',
28
+ dataviewId: WORKSPACE_CONFIG.FISHING_DATAVIEW_SLUG_AIS,
29
+ },
30
+ [WORKSPACE_CONFIG.VMS_DATAVIEW_INSTANCE_ID]: {
31
+ name: 'Apparent fishing effort (VMS)',
32
+ category: 'activity',
33
+ dataviewId: WORKSPACE_CONFIG.FISHING_DATAVIEW_SLUG_VMS,
34
+ },
35
+ 'fishing-effort-vms': {
36
+ name: 'Apparent fishing effort (VMS)',
37
+ category: 'activity',
38
+ dataviewId: WORKSPACE_CONFIG.FISHING_DATAVIEW_SLUG_VMS,
39
+ },
40
+ [WORKSPACE_CONFIG.PRESENCE_DATAVIEW_INSTANCE_ID]: {
41
+ name: 'Vessel presence (AIS)',
42
+ category: 'activity',
43
+ dataviewId: WORKSPACE_CONFIG.PRESENCE_DATAVIEW_SLUG,
44
+ },
45
+ // Detections
46
+ [WORKSPACE_CONFIG.SAR_DATAVIEW_INSTANCE_ID]: {
47
+ name: 'Vessel detections (SAR)',
48
+ category: 'detections',
49
+ dataviewId: WORKSPACE_CONFIG.SAR_DATAVIEW_SLUG,
50
+ },
51
+ [WORKSPACE_CONFIG.SENTINEL2_DATAVIEW_INSTANCE_ID]: {
52
+ name: 'Vessel detections (Sentinel-2 optical)',
53
+ category: 'detections',
54
+ dataviewId: WORKSPACE_CONFIG.SENTINEL2_DATAVIEW_SLUG,
55
+ },
56
+ [WORKSPACE_CONFIG.VIIRS_DATAVIEW_INSTANCE_ID]: {
57
+ name: 'Night light detections (VIIRS EOG)',
58
+ category: 'detections',
59
+ dataviewId: WORKSPACE_CONFIG.VIIRS_MATCH_DATAVIEW_SLUG,
60
+ },
61
+ [WORKSPACE_CONFIG.VIIRS_SKYLIGHT_DATAVIEW_INSTANCE_ID]: {
62
+ name: 'Night light detections (VIIRS Skylight)',
63
+ category: 'detections',
64
+ dataviewId: WORKSPACE_CONFIG.VIIRS_MATCH_SKYLIGHT_DATAVIEW_SLUG,
65
+ },
66
+ // Events
67
+ [WORKSPACE_CONFIG.ENCOUNTER_EVENTS_SOURCE_ID]: {
68
+ name: 'Encounter events',
69
+ category: 'events',
70
+ dataviewId: WORKSPACE_CONFIG.CLUSTER_ENCOUNTER_EVENTS_DATAVIEW_SLUG,
71
+ },
72
+ [WORKSPACE_CONFIG.LOITERING_EVENTS_SOURCE_ID]: {
73
+ name: 'Loitering events',
74
+ category: 'events',
75
+ dataviewId: WORKSPACE_CONFIG.CLUSTER_LOITERING_EVENTS_DATAVIEW_SLUG,
76
+ },
77
+ [WORKSPACE_CONFIG.PORT_VISITS_EVENTS_SOURCE_ID]: {
78
+ name: 'Port visit events',
79
+ category: 'events',
80
+ dataviewId: WORKSPACE_CONFIG.CLUSTER_PORT_VISIT_EVENTS_DATAVIEW_SLUG,
81
+ },
82
+ [WORKSPACE_CONFIG.GAPS_EVENTS_SOURCE_ID]: {
83
+ name: 'AIS off events (gaps)',
84
+ category: 'events',
85
+ dataviewId: WORKSPACE_CONFIG.CLUSTER_GAPS_EVENTS_DATAVIEW_SLUG,
86
+ },
87
+ // Context
88
+ basemap: { name: 'Basemap', category: 'context' },
89
+ 'basemap-labels': { name: 'Basemap labels', category: 'context' },
90
+ eez: {
91
+ name: 'EEZs (Exclusive Economic Zones)',
92
+ category: 'context',
93
+ dataviewId: WORKSPACE_CONFIG.EEZ_DATAVIEW_SLUG,
94
+ },
95
+ mpa: {
96
+ name: 'MPAs (Marine Protected Areas)',
97
+ category: 'context',
98
+ dataviewId: WORKSPACE_CONFIG.MPA_DATAVIEW_SLUG,
99
+ },
100
+ mpatlas: {
101
+ name: 'MPAtlas protection level',
102
+ category: 'context',
103
+ dataviewId: WORKSPACE_CONFIG.MPATLAS_DATAVIEW_SLUG,
104
+ },
105
+ protectedseas: {
106
+ name: 'ProtectedSeas regulations',
107
+ category: 'context',
108
+ dataviewId: WORKSPACE_CONFIG.PROTECTED_SEAS_DATAVIEW_SLUG,
109
+ },
110
+ rfmo: {
111
+ name: 'RFMOs (Tuna Regional Fisheries Management Organizations)',
112
+ category: 'context',
113
+ dataviewId: WORKSPACE_CONFIG.RFMO_DATAVIEW_SLUG,
114
+ },
115
+ 'fao-areas': {
116
+ name: 'FAO major fishing areas',
117
+ category: 'context',
118
+ dataviewId: WORKSPACE_CONFIG.FAO_AREAS_DATAVIEW_SLUG,
119
+ },
120
+ 'fao-major': {
121
+ name: 'FAO major fishing areas',
122
+ category: 'context',
123
+ dataviewId: WORKSPACE_CONFIG.FAO_AREAS_DATAVIEW_SLUG,
124
+ },
125
+ graticules: {
126
+ name: 'Latitude/longitude grids',
127
+ category: 'context',
128
+ dataviewId: WORKSPACE_CONFIG.GRATICULES_DATAVIEW_SLUG,
129
+ },
130
+ 'high-seas': {
131
+ name: 'High seas',
132
+ category: 'context',
133
+ dataviewId: WORKSPACE_CONFIG.HIGH_SEAS_DATAVIEW_SLUG,
134
+ },
135
+ 'high-seas-pockets': {
136
+ name: 'High seas pockets',
137
+ category: 'context',
138
+ dataviewId: WORKSPACE_CONFIG.HIGH_SEAS_POCKETS_DATAVIEW_SLUG,
139
+ },
140
+ 'eez-areas-12nm': {
141
+ name: '12 nautical miles zones',
142
+ category: 'context',
143
+ dataviewId: WORKSPACE_CONFIG.EEZ_AREAS_12NM_DATAVIEW_SLUG,
144
+ },
145
+ 'offshore-fixed-infrastructure': {
146
+ name: 'Offshore fixed infrastructure',
147
+ category: 'context',
148
+ dataviewId: WORKSPACE_CONFIG.FIXED_SAR_INFRASTRUCTURE,
149
+ },
150
+ 'port-locations': {
151
+ name: 'Port locations (AIS)',
152
+ category: 'context',
153
+ dataviewId: WORKSPACE_CONFIG.PORTS_AIS_DATAVIEW_SLUG,
154
+ },
155
+ 'port-locations-vms': {
156
+ name: 'Port locations (VMS)',
157
+ category: 'context',
158
+ dataviewId: WORKSPACE_CONFIG.PORTS_VMS_DATAVIEW_SLUG,
159
+ },
160
+ 'paa-duke': {
161
+ name: 'Preferential access areas (Duke)',
162
+ category: 'context',
163
+ dataviewId: WORKSPACE_CONFIG.PAA_DUKE_DATAVIEW_SLUG,
164
+ },
165
+ 'gfcm-fao': {
166
+ name: 'GFCM FAO areas',
167
+ category: 'context',
168
+ dataviewId: WORKSPACE_CONFIG.GFCM_FAO_DATAVIEW_SLUG,
169
+ },
170
+ 'dsm-isa-leasing-areas': {
171
+ name: 'Deep sea mining ISA leasing areas',
172
+ category: 'context',
173
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_CONTEXT_DATAVIEW_SLUG,
174
+ },
175
+ immas: {
176
+ name: 'Important Marine Mammal Areas',
177
+ category: 'context',
178
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_CONTEXT_DATAVIEW_SLUG,
179
+ },
180
+ ebsas: {
181
+ name: 'Ecologically or Biologically Significant Areas',
182
+ category: 'context',
183
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_CONTEXT_DATAVIEW_SLUG,
184
+ },
185
+ // Environment
186
+ [WORKSPACE_CONFIG.BATHYMETRY_DATAVIEW_PREFIX]: {
187
+ name: 'Bathymetry',
188
+ category: 'environment',
189
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_STATIC_DATAVIEW_SLUG,
190
+ },
191
+ currents: {
192
+ name: 'Currents',
193
+ category: 'environment',
194
+ dataviewId: WORKSPACE_CONFIG.CURRENTS_DATAVIEW_SLUG,
195
+ },
196
+ winds: {
197
+ name: 'Winds',
198
+ category: 'environment',
199
+ dataviewId: WORKSPACE_CONFIG.WINDS_DATAVIEW_SLUG,
200
+ },
201
+ chlorophyl: {
202
+ name: 'Chlorophyll-a concentration',
203
+ category: 'environment',
204
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
205
+ },
206
+ nitrate: {
207
+ name: 'Nitrate concentration',
208
+ category: 'environment',
209
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
210
+ },
211
+ oxygen: {
212
+ name: 'Dissolved oxygen',
213
+ category: 'environment',
214
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
215
+ },
216
+ ph: {
217
+ name: 'pH',
218
+ category: 'environment',
219
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
220
+ },
221
+ phosphate: {
222
+ name: 'Phosphate concentration',
223
+ category: 'environment',
224
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
225
+ },
226
+ salinity: {
227
+ name: 'Salinity',
228
+ category: 'environment',
229
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
230
+ },
231
+ sst: {
232
+ name: 'Sea surface temperature',
233
+ category: 'environment',
234
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
235
+ },
236
+ 'sst-anomalies': {
237
+ name: 'Sea surface temperature anomalies',
238
+ category: 'environment',
239
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
240
+ },
241
+ 'sst-anomalies-min': {
242
+ name: 'Sea surface temperature anomalies (min)',
243
+ category: 'environment',
244
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
245
+ },
246
+ 'sst-anomalies-max': {
247
+ name: 'Sea surface temperature anomalies (max)',
248
+ category: 'environment',
249
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
250
+ },
251
+ thgt: {
252
+ name: 'Wave height',
253
+ category: 'environment',
254
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_HEATMAP_ENVIRONMENT_DATAVIEW_SLUG,
255
+ },
256
+ 'marine-ecoregions': {
257
+ name: 'Marine ecoregions',
258
+ category: 'environment',
259
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_GFW_ENVIRONMENT_DATAVIEW_SLUG,
260
+ },
261
+ mangroves: {
262
+ name: 'Mangroves',
263
+ category: 'environment',
264
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_GFW_ENVIRONMENT_DATAVIEW_SLUG,
265
+ },
266
+ seamounts: {
267
+ name: 'Seamounts',
268
+ category: 'environment',
269
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_GFW_ENVIRONMENT_DATAVIEW_SLUG,
270
+ },
271
+ 'coral-reefs': {
272
+ name: 'Coral reefs',
273
+ category: 'environment',
274
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_GFW_ENVIRONMENT_DATAVIEW_SLUG,
275
+ },
276
+ seagrasses: {
277
+ name: 'Seagrasses',
278
+ category: 'environment',
279
+ dataviewId: WORKSPACE_CONFIG.TEMPLATE_GFW_ENVIRONMENT_DATAVIEW_SLUG,
280
+ },
281
+ }
282
+
283
+ const LIBRARY_ID_SUFFIX_REGEX = new RegExp(`${WORKSPACE_CONFIG.LAYER_LIBRARY_ID_SEPARATOR}\\d+$`)
284
+
285
+ /**
286
+ * Resolves a dataview instance id to layer info. Handles the app id conventions:
287
+ * `context-layer-` prefix, `__<timestamp>` suffix (layer-library additions) and
288
+ * `vessel-` prefix (vessel track layers)
289
+ */
290
+ export const getLayerInfo = (instanceId: string): LayerInfo => {
291
+ const baseId = instanceId.replace(LIBRARY_ID_SUFFIX_REGEX, '')
292
+ const contextId = baseId.startsWith(WORKSPACE_CONFIG.CONTEXT_LAYER_INSTANCE_PREFIX)
293
+ ? baseId.replace(WORKSPACE_CONFIG.CONTEXT_LAYER_INSTANCE_PREFIX, '')
294
+ : baseId
295
+ if (LAYERS_DICTIONARY[contextId]) {
296
+ return LAYERS_DICTIONARY[contextId]
297
+ }
298
+ if (baseId.startsWith(VESSEL_DATAVIEW_INSTANCE_PREFIX)) {
299
+ return {
300
+ name: `Vessel track (${baseId.replace(VESSEL_DATAVIEW_INSTANCE_PREFIX, '')})`,
301
+ category: 'activity',
302
+ }
303
+ }
304
+ return { name: baseId, category: 'context' }
305
+ }
@@ -0,0 +1,79 @@
1
+ import { LAYER_LIBRARY_ID_SEPARATOR } from '@fishing-map/config'
2
+
3
+ import type { BaseUrlWorkspace } from '@globalfishingwatch/dataviews-client'
4
+ import { stringifyWorkspace } from '@globalfishingwatch/dataviews-client'
5
+ import { getDateInIntervalResolution } from '@globalfishingwatch/deck-layers'
6
+ import { getFourwingsInterval } from '@globalfishingwatch/deck-loaders'
7
+
8
+ import { resolveDataviewSlug } from './config'
9
+ import { getLayerInfo } from './dictionary'
10
+ import type { MapRoute, RouteNavigation } from './routes'
11
+ import { buildRoutePath, DEFAULT_BASENAME, getRouteNavigation } from './routes'
12
+
13
+ // Layer-library instances (id convention `<libraryId>__<unique>`) require a dataviewId;
14
+ // fill it from the dictionary when missing so agents don't have to know slugs
15
+ const withDataviewId = (instance: any) => {
16
+ if (instance?.dataviewId) {
17
+ return { ...instance, dataviewId: resolveDataviewSlug(String(instance.dataviewId)) }
18
+ }
19
+ if (instance?.id?.includes(LAYER_LIBRARY_ID_SEPARATOR)) {
20
+ const { dataviewId } = getLayerInfo(instance.id)
21
+ if (dataviewId) {
22
+ return { ...instance, dataviewId }
23
+ }
24
+ }
25
+ return instance
26
+ }
27
+
28
+ // Snaps start/end to the fourwings interval resolution the app will render with
29
+ // (month boundaries for ~year ranges, day for short ranges) — same functions the map uses
30
+ const withSnappedTimeRange = (state: MapState): MapState => {
31
+ const start = typeof state.start === 'string' ? Date.parse(state.start) : NaN
32
+ const end = typeof state.end === 'string' ? Date.parse(state.end) : NaN
33
+ if (!Number.isFinite(start) || !Number.isFinite(end)) {
34
+ return state
35
+ }
36
+ const interval = getFourwingsInterval(start, end)
37
+ return {
38
+ ...state,
39
+ start: new Date(getDateInIntervalResolution(start, interval)).toISOString(),
40
+ end: new Date(getDateInIntervalResolution(end, interval)).toISOString(),
41
+ }
42
+ }
43
+
44
+ export type MapState = BaseUrlWorkspace & Record<string, unknown>
45
+
46
+ export type EncodeMapUrlInput = {
47
+ route: MapRoute
48
+ state?: MapState
49
+ /** App basename prepended to `path`, defaults to '/map' */
50
+ basename?: string
51
+ }
52
+
53
+ export type EncodeMapUrlResult = {
54
+ /** TanStack Router navigation config to navigate from inside the map app */
55
+ navigation: RouteNavigation & { search: MapState }
56
+ /** Path + encoded query. External navigation = prepend origin (https://globalfishingwatch.org) */
57
+ path: string
58
+ }
59
+
60
+ export const encodeMapUrl = ({
61
+ route,
62
+ state = {},
63
+ basename = DEFAULT_BASENAME,
64
+ }: EncodeMapUrlInput): EncodeMapUrlResult => {
65
+ const navigation = getRouteNavigation(route)
66
+ state = withSnappedTimeRange(state)
67
+ if (state.dataviewInstances) {
68
+ state = {
69
+ ...state,
70
+ dataviewInstances: state.dataviewInstances.map(withDataviewId),
71
+ }
72
+ }
73
+ const query = stringifyWorkspace(state)
74
+ const path = `${basename}${buildRoutePath(navigation)}${query ? `?${query}` : ''}`
75
+ return {
76
+ navigation: { ...navigation, search: state },
77
+ path,
78
+ }
79
+ }
@@ -0,0 +1,4 @@
1
+ export * from './config'
2
+ export * from './dictionary'
3
+ export * from './encode'
4
+ export * from './routes'