From 602074b5d262c3acce67193e25837fb6b3e30f90 Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Mon, 15 Apr 2024 13:15:16 +0200 Subject: [PATCH 1/2] show highlighted track segments and events on top of the rest --- .../src/layers/vessel/VesselEventsLayer.ts | 57 +++++++++++++++++-- .../src/layers/vessel/VesselLayer.ts | 11 ++-- .../src/layers/vessel/VesselTrackLayer.ts | 14 +++-- .../src/layers/vessel/vessel.config.ts | 2 + 4 files changed, 69 insertions(+), 15 deletions(-) diff --git a/libs/deck-layers/src/layers/vessel/VesselEventsLayer.ts b/libs/deck-layers/src/layers/vessel/VesselEventsLayer.ts index f051a54638..91e23eaa83 100644 --- a/libs/deck-layers/src/layers/vessel/VesselEventsLayer.ts +++ b/libs/deck-layers/src/layers/vessel/VesselEventsLayer.ts @@ -7,14 +7,18 @@ import { } from '@deck.gl/core' import { ScatterplotLayer, ScatterplotLayerProps } from '@deck.gl/layers' import { EventTypes } from '@globalfishingwatch/api-types' -import { EVENT_SHAPES, SHAPES_ORDINALS } from './vessel.config' +import { DEFAULT_HIGHLIGHT_COLOR_VEC, EVENT_SHAPES, SHAPES_ORDINALS } from './vessel.config' export type _VesselEventsLayerProps = { type: EventTypes filterRange?: Array visibleEvents?: EventTypes[] highlightEventIds?: string[] + highlightStartTime?: number + highlightEndTime?: number getShape?: AccessorFunction + getStart?: AccessorFunction + getEnd?: AccessorFunction getPosition?: AccessorFunction | Position getFilterValue?: AccessorFunction getPickingInfo?: AccessorFunction @@ -39,6 +43,8 @@ const defaultProps: DefaultProps = { type: 'accessor', value: (d) => EVENT_SHAPES[d.type as EventTypes] ?? EVENT_SHAPES.fishing, }, + getStart: { type: 'accessor', value: (d) => d.start }, + getEnd: { type: 'accessor', value: (d) => d.end }, getFillColor: { type: 'accessor', value: (d) => [255, 255, 255] }, getPosition: { type: 'accessor', value: (d) => d.coordinates }, getPickingInfo: { type: 'accessor', value: ({ info }) => info }, @@ -61,6 +67,20 @@ export class VesselEventsLayer extends Scatterplot size: 1, accessor: 'getShape', }, + start: { + size: 1, + accessor: 'getStart', + shaderAttributes: { + instanceStart: {}, + }, + }, + end: { + size: 1, + accessor: 'getEnd', + shaderAttributes: { + instanceEnd: {}, + }, + }, }) } } @@ -70,16 +90,33 @@ export class VesselEventsLayer extends Scatterplot ...super.getShaders(), inject: { 'vs:#decl': ` + uniform float highlightStartTime; + uniform float highlightEndTime; + in float instanceShapes; in float instanceId; + in float instanceStart; + in float instanceEnd; + + out float vStart; + out float vEnd; out float vShape; `, 'vs:#main-end': ` vShape = instanceShapes; + vStart = instanceStart; + vEnd = instanceEnd; + if(vStart > highlightStartTime && vEnd < highlightEndTime) { + gl_Position.z = 1.0; + } `, 'fs:#decl': ` uniform mat3 hueTransform; + uniform float highlightStartTime; + uniform float highlightEndTime; in float vShape; + in float vStart; + in float vEnd; const int SHAPE_CIRCLE = ${SHAPES_ORDINALS.circle}; const int SHAPE_SQUARE = ${SHAPES_ORDINALS.square}; const int SHAPE_DIAMOND = ${SHAPES_ORDINALS.diamond}; @@ -91,11 +128,14 @@ export class VesselEventsLayer extends Scatterplot if (shape == SHAPE_SQUARE) { if (uv.x > 0.7 || uv.y > 0.7) discard; } else if (shape == SHAPE_DIAMOND) { - if (uv.x + uv.y > 1.0) discard; + if (uv.x + uv.y > 1.0) discard; } else if (shape == SHAPE_DIAMOND_STROKE) { - if (uv.x + uv.y > 1.0 || uv.x + uv.y < 0.7) { - discard; - } + if (uv.x + uv.y > 1.0 || uv.x + uv.y < 0.7) { + discard; + } + } + if (vStart > highlightStartTime && vEnd < highlightEndTime) { + color = vec4(${DEFAULT_HIGHLIGHT_COLOR_VEC.join(',')}); } `, }, @@ -111,6 +151,13 @@ export class VesselEventsLayer extends Scatterplot } draw(params: any) { + const { highlightStartTime, highlightEndTime } = this.props + + params.uniforms = { + ...params.uniforms, + highlightStartTime: highlightStartTime ? highlightStartTime : 0, + highlightEndTime: highlightEndTime ? highlightEndTime : 0, + } super.draw(params) } } diff --git a/libs/deck-layers/src/layers/vessel/VesselLayer.ts b/libs/deck-layers/src/layers/vessel/VesselLayer.ts index 8f6bfd27cc..8a63f246cc 100644 --- a/libs/deck-layers/src/layers/vessel/VesselLayer.ts +++ b/libs/deck-layers/src/layers/vessel/VesselLayer.ts @@ -125,15 +125,18 @@ export class VesselLayer extends CompositeLayer { onError: this.onSublayerError, loaders: [VesselEventsLoader], pickable: true, + highlightStartTime: this.props.highlightStartTime, + highlightEndTime: this.props.highlightEndTime, getPolygonOffset: (params: any) => getLayerGroupOffset(LayerGroup.Point, params), getFillColor: (d: any): Color => { - if (highlightEventIds?.includes(d.id)) { - return EVENTS_COLORS.highlight - } + // if (highlightEventIds?.includes(d.id)) { + // return EVENTS_COLORS.highlight + // } return d.type === EventTypes.Fishing ? this.props.color : EVENTS_COLORS[d.type] }, updateTriggers: { - getFillColor: [this.props.highlightEventIds, this.props.color], + // getFillColor: [this.props.highlightEventIds, this.props.color], + getFillColor: [this.props.color], }, radiusUnits: 'pixels', getRadius: (d: any) => { diff --git a/libs/deck-layers/src/layers/vessel/VesselTrackLayer.ts b/libs/deck-layers/src/layers/vessel/VesselTrackLayer.ts index 10a6382f4f..bacb71138e 100644 --- a/libs/deck-layers/src/layers/vessel/VesselTrackLayer.ts +++ b/libs/deck-layers/src/layers/vessel/VesselTrackLayer.ts @@ -3,6 +3,7 @@ import { AccessorFunction, ChangeFlags, DefaultProps, UpdateParameters } from '@ import { PathLayer, PathLayerProps } from '@deck.gl/layers' import { TrackSegment } from '@globalfishingwatch/api-types' import { VesselTrackData } from '@globalfishingwatch/deck-loaders' +import { DEFAULT_HIGHLIGHT_COLOR_VEC } from './vessel.config' /** Properties added by VesselTrackLayer. */ export type _VesselTrackLayerProps = { @@ -50,7 +51,6 @@ export type _VesselTrackLayerProps = { // not needed anymore as the highlighted color is fixed // const DEFAULT_HIGHLIGHT_COLOR_RGBA = [255, 255, 255, 255] as Color -const DEFAULT_HIGHLIGHT_COLOR_VEC = [1.0, 1.0, 1.0, 1.0] const defaultProps: DefaultProps = { _pathType: 'open', endTime: { type: 'number', value: 0, min: 0 }, @@ -81,14 +81,19 @@ export class VesselTrackLayer extends PathLayer< const shaders = super.getShaders() shaders.inject = { 'vs:#decl': ` + uniform float highlightStartTime; + uniform float highlightEndTime; + in float instanceTimestamps; - // in vec4 instanceHighlightColor; out float vTime; // out vec4 vHighlightColor; `, // Timestamp of the vertex 'vs:#main-end': ` vTime = instanceTimestamps; + if(vTime > highlightStartTime && vTime < highlightEndTime) { + gl_Position.z = 1.0; + } // vHighlightColor = vec4(instanceHighlightColor.rgb, instanceHighlightColor.a); `, 'fs:#decl': ` @@ -106,11 +111,8 @@ export class VesselTrackLayer extends PathLayer< } `, 'fs:DECKGL_FILTER_COLOR': ` - if (vTime < highlightStartTime || vTime > highlightEndTime) { - color = color; - } else { + if (vTime > highlightStartTime && vTime < highlightEndTime) { // color = vHighlightColor; - // TODO:deck position this on top of the other vessel layers to ensure highlgihts is always visible color = vec4(${DEFAULT_HIGHLIGHT_COLOR_VEC.join(',')}); } `, diff --git a/libs/deck-layers/src/layers/vessel/vessel.config.ts b/libs/deck-layers/src/layers/vessel/vessel.config.ts index e844dffc73..f784f2e0d3 100644 --- a/libs/deck-layers/src/layers/vessel/vessel.config.ts +++ b/libs/deck-layers/src/layers/vessel/vessel.config.ts @@ -26,3 +26,5 @@ export const EVENTS_COLORS: Record = { port_visit: hexToDeckColor('#99EEFF'), highlight: hexToDeckColor('#ffffff'), } + +export const DEFAULT_HIGHLIGHT_COLOR_VEC = [1.0, 1.0, 1.0, 1.0] From 1c922cf10cdad761ecdb59c485c91cb2d652403d Mon Sep 17 00:00:00 2001 From: satellitestudiodesign Date: Mon, 15 Apr 2024 14:03:06 +0200 Subject: [PATCH 2/2] request events in yearly chunks --- .../src/layers/vessel/VesselLayer.ts | 118 ++++++++++-------- .../src/layers/vessel/vessel.config.ts | 1 + .../src/layers/vessel/vessel.utils.ts | 2 +- 3 files changed, 68 insertions(+), 53 deletions(-) diff --git a/libs/deck-layers/src/layers/vessel/VesselLayer.ts b/libs/deck-layers/src/layers/vessel/VesselLayer.ts index 8a63f246cc..d664ea408e 100644 --- a/libs/deck-layers/src/layers/vessel/VesselLayer.ts +++ b/libs/deck-layers/src/layers/vessel/VesselLayer.ts @@ -17,8 +17,8 @@ import { getLayerGroupOffset, LayerGroup } from '../../utils' import { BaseLayerProps } from '../../types' import { VesselEventsLayer, _VesselEventsLayerProps } from './VesselEventsLayer' import { VesselTrackLayer, _VesselTrackLayerProps } from './VesselTrackLayer' -import { getVesselTrackThunks } from './vessel.utils' -import { EVENTS_COLORS, TRACK_LAYER_TYPE } from './vessel.config' +import { getVesselResourceThunks } from './vessel.utils' +import { EVENTS_COLORS, EVENT_LAYER_TYPE, TRACK_LAYER_TYPE } from './vessel.config' import { VesselDataStatus, VesselDataType, @@ -69,21 +69,23 @@ export class VesselLayer extends CompositeLayer { } _getVesselTrackLayers() { - if (!this.props.trackUrl) { - console.warn('trackUrl needed for vessel layer') + const { trackUrl, visible, startTime, endTime, color, highlightStartTime, highlightEndTime } = + this.props + if (!trackUrl || !visible) { + if (!trackUrl) console.warn('trackUrl needed for vessel layer') return [] } - const chunks = getVesselTrackThunks(this.props.startTime, this.props.endTime) + const chunks = getVesselResourceThunks(startTime, endTime) return chunks.map(({ start, end }) => { const chunkId = `${TRACK_LAYER_TYPE}-${start}-${end}` - const trackUrl = new URL(this.props.trackUrl as string) - trackUrl.searchParams.append('start-date', start as string) - trackUrl.searchParams.append('end-date', end as string) + const trackUrlObject = new URL(trackUrl as string) + trackUrlObject.searchParams.append('start-date', start as string) + trackUrlObject.searchParams.append('end-date', end as string) return new VesselTrackLayer( this.getSubLayerProps({ id: chunkId, - visible: this.props.visible, - data: trackUrl.toString(), + visible, + data: trackUrlObject.toString(), type: TRACK_LAYER_TYPE, loaders: [VesselTrackLoader], _pathType: 'open', @@ -93,11 +95,11 @@ export class VesselLayer extends CompositeLayer { wrapLongitude: true, jointRounded: true, capRounded: true, - getColor: this.props.color, - startTime: this.props.startTime, - endTime: this.props.endTime, - highlightStartTime: this.props.highlightStartTime, - highlightEndTime: this.props.highlightEndTime, + getColor: color, + startTime, + endTime, + highlightStartTime, + highlightEndTime, getPolygonOffset: (params: any) => getLayerGroupOffset(LayerGroup.Track, params), onError: this.onSublayerError, }) @@ -105,55 +107,67 @@ export class VesselLayer extends CompositeLayer { }) } - _getVesselEventsLayer(): VesselEventsLayer[] { - const { visible, visibleEvents, startTime, endTime, highlightEventIds } = this.props + _getVesselEventLayers(): VesselEventsLayer[] { + const { + visible, + visibleEvents, + startTime, + endTime, + highlightEventIds, + events, + highlightStartTime, + highlightEndTime, + color, + } = this.props if (!visible) { return [] } + const chunks = getVesselResourceThunks(startTime, endTime) // return one layer with all events if we are consuming the data object from app resources - return this.props.events?.flatMap(({ url, type }) => { + return events?.flatMap(({ url, type }) => { const visible = visibleEvents?.includes(type) if (!visible) { return [] } - return new VesselEventsLayer( - this.getSubLayerProps({ - id: type, - data: url, - visible, - type, - onError: this.onSublayerError, - loaders: [VesselEventsLoader], - pickable: true, - highlightStartTime: this.props.highlightStartTime, - highlightEndTime: this.props.highlightEndTime, - getPolygonOffset: (params: any) => getLayerGroupOffset(LayerGroup.Point, params), - getFillColor: (d: any): Color => { - // if (highlightEventIds?.includes(d.id)) { - // return EVENTS_COLORS.highlight - // } - return d.type === EventTypes.Fishing ? this.props.color : EVENTS_COLORS[d.type] - }, - updateTriggers: { - // getFillColor: [this.props.highlightEventIds, this.props.color], - getFillColor: [this.props.color], - }, - radiusUnits: 'pixels', - getRadius: (d: any) => { - const highlightOffset = highlightEventIds?.includes(d.id) ? 3 : 0 - return (d.type === EventTypes.Fishing ? 3 : 6) + highlightOffset - }, - getFilterValue: (d: VesselDeckLayersEventData) => [d.start, d.end] as any, - filterRange: [[startTime, endTime] as any, [startTime, endTime] as any], - extensions: [new DataFilterExtension({ filterSize: 2 }) as any], - }) - ) + return chunks.map(({ start, end }) => { + const chunkId = `${EVENT_LAYER_TYPE}-${type}-${start}-${end}` + const eventUrl = new URL(url as string) + eventUrl.searchParams.append('start-date', start as string) + eventUrl.searchParams.append('end-date', end as string) + return new VesselEventsLayer( + this.getSubLayerProps({ + id: chunkId, + data: eventUrl.toString(), + visible, + type, + onError: this.onSublayerError, + loaders: [VesselEventsLoader], + pickable: true, + highlightStartTime, + highlightEndTime, + getPolygonOffset: (params: any) => getLayerGroupOffset(LayerGroup.Point, params), + getFillColor: (d: any): Color => { + return d.type === EventTypes.Fishing ? color : EVENTS_COLORS[d.type] + }, + updateTriggers: { + getFillColor: [color], + }, + radiusUnits: 'pixels', + getRadius: (d: any) => { + const highlightOffset = highlightEventIds?.includes(d.id) ? 3 : 0 + return (d.type === EventTypes.Fishing ? 3 : 6) + highlightOffset + }, + getFilterValue: (d: VesselDeckLayersEventData) => [d.start, d.end] as any, + filterRange: [[startTime, endTime] as any, [startTime, endTime] as any], + extensions: [new DataFilterExtension({ filterSize: 2 }) as any], + }) + ) + }) }) } renderLayers(): Layer<{}> | LayersList { - return [...this._getVesselTrackLayers(), ...this._getVesselEventsLayer()] - // return this._getVesselEventsLayer() + return [...this._getVesselTrackLayers(), ...this._getVesselEventLayers()] } getTrackLayers() { diff --git a/libs/deck-layers/src/layers/vessel/vessel.config.ts b/libs/deck-layers/src/layers/vessel/vessel.config.ts index f784f2e0d3..99a33621a1 100644 --- a/libs/deck-layers/src/layers/vessel/vessel.config.ts +++ b/libs/deck-layers/src/layers/vessel/vessel.config.ts @@ -3,6 +3,7 @@ import { EventTypes } from '@globalfishingwatch/api-types' import { hexToDeckColor } from '../../utils' export const TRACK_LAYER_TYPE = 'track' +export const EVENT_LAYER_TYPE = 'event' type EventShape = 'circle' | 'square' | 'diamond' | 'diamondStroke' export const SHAPES_ORDINALS: Record = { diff --git a/libs/deck-layers/src/layers/vessel/vessel.utils.ts b/libs/deck-layers/src/layers/vessel/vessel.utils.ts index c066b8cf5f..d21c1fd0b4 100644 --- a/libs/deck-layers/src/layers/vessel/vessel.utils.ts +++ b/libs/deck-layers/src/layers/vessel/vessel.utils.ts @@ -1,7 +1,7 @@ import { DateTime, Duration } from 'luxon' import { getUTCDateTime } from '../../utils/dates' -export const getVesselTrackThunks = (start: number, end: number) => { +export const getVesselResourceThunks = (start: number, end: number) => { const startDT = getUTCDateTime(start) const endDT = getUTCDateTime(end) const yearsDelta = Math.ceil(Duration.fromMillis(+endDT - +startDT).as('years'))