Skip to content

Commit

Permalink
Merge pull request #2615 from GlobalFishingWatch/deck-migration/track…
Browse files Browse the repository at this point in the history
…-and-events-highlight

show highlighted track segments and events on top of the rest
  • Loading branch information
j8seangel authored Apr 15, 2024
2 parents d2bc7d2 + 1c922cf commit 69a0bef
Show file tree
Hide file tree
Showing 5 changed files with 130 additions and 61 deletions.
57 changes: 52 additions & 5 deletions libs/deck-layers/src/layers/vessel/VesselEventsLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DataT = any> = {
type: EventTypes
filterRange?: Array<number>
visibleEvents?: EventTypes[]
highlightEventIds?: string[]
highlightStartTime?: number
highlightEndTime?: number
getShape?: AccessorFunction<DataT, number>
getStart?: AccessorFunction<DataT, number>
getEnd?: AccessorFunction<DataT, number>
getPosition?: AccessorFunction<DataT, Position> | Position
getFilterValue?: AccessorFunction<DataT, number>
getPickingInfo?: AccessorFunction<DataT, string>
Expand All @@ -39,6 +43,8 @@ const defaultProps: DefaultProps<VesselEventsLayerProps> = {
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 },
Expand All @@ -61,6 +67,20 @@ export class VesselEventsLayer<DataT = any, ExtraProps = {}> extends Scatterplot
size: 1,
accessor: 'getShape',
},
start: {
size: 1,
accessor: 'getStart',
shaderAttributes: {
instanceStart: {},
},
},
end: {
size: 1,
accessor: 'getEnd',
shaderAttributes: {
instanceEnd: {},
},
},
})
}
}
Expand All @@ -70,16 +90,33 @@ export class VesselEventsLayer<DataT = any, ExtraProps = {}> 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};
Expand All @@ -91,11 +128,14 @@ export class VesselEventsLayer<DataT = any, ExtraProps = {}> 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(',')});
}
`,
},
Expand All @@ -111,6 +151,13 @@ export class VesselEventsLayer<DataT = any, ExtraProps = {}> 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)
}
}
115 changes: 66 additions & 49 deletions libs/deck-layers/src/layers/vessel/VesselLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -69,21 +69,23 @@ export class VesselLayer extends CompositeLayer<VesselLayerProps & LayerProps> {
}

_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<any, { type: VesselDataType }>(
this.getSubLayerProps({
id: chunkId,
visible: this.props.visible,
data: trackUrl.toString(),
visible,
data: trackUrlObject.toString(),
type: TRACK_LAYER_TYPE,
loaders: [VesselTrackLoader],
_pathType: 'open',
Expand All @@ -93,64 +95,79 @@ export class VesselLayer extends CompositeLayer<VesselLayerProps & LayerProps> {
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,
})
)
})
}

_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<VesselDeckLayersEventData[]>(
this.getSubLayerProps({
id: type,
data: url,
visible,
type,
onError: this.onSublayerError,
loaders: [VesselEventsLoader],
pickable: true,
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],
},
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<VesselDeckLayersEventData[]>(
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() {
Expand Down
14 changes: 8 additions & 6 deletions libs/deck-layers/src/layers/vessel/VesselTrackLayer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<DataT = any> = {
Expand Down Expand Up @@ -50,7 +51,6 @@ export type _VesselTrackLayerProps<DataT = any> = {
// 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<VesselTrackLayerProps> = {
_pathType: 'open',
endTime: { type: 'number', value: 0, min: 0 },
Expand Down Expand Up @@ -81,14 +81,19 @@ export class VesselTrackLayer<DataT = any, ExtraProps = {}> 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': `
Expand All @@ -106,11 +111,8 @@ export class VesselTrackLayer<DataT = any, ExtraProps = {}> 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(',')});
}
`,
Expand Down
3 changes: 3 additions & 0 deletions libs/deck-layers/src/layers/vessel/vessel.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<EventShape, number> = {
Expand All @@ -26,3 +27,5 @@ export const EVENTS_COLORS: Record<string, Color> = {
port_visit: hexToDeckColor('#99EEFF'),
highlight: hexToDeckColor('#ffffff'),
}

export const DEFAULT_HIGHLIGHT_COLOR_VEC = [1.0, 1.0, 1.0, 1.0]
2 changes: 1 addition & 1 deletion libs/deck-layers/src/layers/vessel/vessel.utils.ts
Original file line number Diff line number Diff line change
@@ -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'))
Expand Down

0 comments on commit 69a0bef

Please sign in to comment.