Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fishing map/port visit clusters #2839

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,14 @@ import {
BASEMAP_DATAVIEW_INSTANCE_ID,
FIXED_SAR_INFRASTRUCTURE,
DEFAULT_WORKSPACE_CATEGORY,
CLUSTER_PORT_VISIT_EVENTS_DATAVIEW_SLUG,
CLUSTER_LOITERING_EVENTS_DATAVIEW_SLUG,
} from 'data/workspaces'
import { ENCOUNTER_EVENTS_SOURCE_ID } from 'features/dataviews/dataviews.utils'
import {
ENCOUNTER_EVENTS_SOURCE_ID,
LOITERING_EVENTS_SOURCE_ID,
PORT_VISITS_EVENTS_SOURCE_ID,
} from 'features/dataviews/dataviews.utils'
import { OFFSHORE_FIXED_INFRASTRUCTURE_LAYER_ID } from 'features/map/map.config'
import { HIGHLIGHT_DATAVIEW_INSTANCE_ID } from 'features/workspace/highlight-panel/highlight-panel.content'
import { WorkspaceState } from 'types'
Expand Down Expand Up @@ -107,6 +113,20 @@ const workspace: Workspace<WorkspaceState> = {
visible: false,
},
},
{
id: LOITERING_EVENTS_SOURCE_ID,
dataviewId: CLUSTER_LOITERING_EVENTS_DATAVIEW_SLUG,
config: {
visible: false,
},
},
{
id: PORT_VISITS_EVENTS_SOURCE_ID,
dataviewId: CLUSTER_PORT_VISIT_EVENTS_DATAVIEW_SLUG,
config: {
visible: false,
},
},
{
id: 'context-layer-graticules',
config: {
Expand Down
2 changes: 2 additions & 0 deletions apps/fishing-map/data/workspaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ export const FAO_AREAS_DATAVIEW_INSTANCE_ID = 'context-layer-fao-areas'
// Workspaces dataviews
export const FISHING_DATAVIEW_SLUG = 'apparent-fishing-effort-v-3'
export const CLUSTER_ENCOUNTER_EVENTS_DATAVIEW_SLUG = 'encounter-cluster-events-v-3'
export const CLUSTER_LOITERING_EVENTS_DATAVIEW_SLUG = 'loitering-cluster-events-v-3'
export const CLUSTER_PORT_VISIT_EVENTS_DATAVIEW_SLUG = 'port-visit-cluster-events-v-3'
export const VIIRS_MATCH_DATAVIEW_SLUG = 'viirs-match-v-3'
export const SAR_DATAVIEW_SLUG = 'sar-v-3'
export const PRESENCE_DATAVIEW_SLUG = 'presence-activity-v-3'
Expand Down
63 changes: 62 additions & 1 deletion apps/fishing-map/features/dataviews/dataviews.mock.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Dataview, DataviewType, DataviewCategory, EndpointId } from '@globalfishingwatch/api-types'
import { CLUSTER_ENCOUNTER_EVENTS_DATAVIEW_SLUG } from 'data/workspaces'
import {
CLUSTER_ENCOUNTER_EVENTS_DATAVIEW_SLUG,
CLUSTER_LOITERING_EVENTS_DATAVIEW_SLUG,
CLUSTER_PORT_VISIT_EVENTS_DATAVIEW_SLUG,
} from 'data/workspaces'

const dataviews: Dataview[] = [
{
Expand All @@ -10,6 +14,9 @@ const dataviews: Dataview[] = [
config: {
type: 'FOURWINGS_TILE_CLUSTER',
color: '#FAE9A0',
clusterMaxZoomLevels: {
default: 6,
},
},
datasetsConfig: [
{
Expand All @@ -26,6 +33,60 @@ const dataviews: Dataview[] = [
updatedAt: '2024-05-16T08:21:11.723Z',
category: DataviewCategory.Events,
},
{
id: 22222222,
name: 'Loitering cluster events pipe 3',
slug: CLUSTER_LOITERING_EVENTS_DATAVIEW_SLUG,
app: 'fishing-map',
config: {
type: 'FOURWINGS_TILE_CLUSTER',
color: '#CEA9F9',
clusterMaxZoomLevels: {
default: 6,
},
},
datasetsConfig: [
{
filters: {},
params: [],
endpoint: 'events-cluster-tiles',
datasetId: 'public-global-loitering-events:v3.0',
},
],
description: 'Loitering cluster events',
createdAt: '2024-05-16T08:21:11.723Z',
updatedAt: '2024-05-16T08:21:11.723Z',
category: DataviewCategory.Events,
},
{
id: 33333333,
name: 'Port visits cluster events pipe 3',
slug: CLUSTER_PORT_VISIT_EVENTS_DATAVIEW_SLUG,
app: 'fishing-map',
config: {
type: 'FOURWINGS_TILE_CLUSTER',
color: '#9AEEFF',
clusterMaxZoomLevels: {
country: 3,
// port: 6,
default: 8,
},
},
datasetsConfig: [
{
params: [],
filters: {
confidence: 2,
},
endpoint: 'events-cluster-tiles',
datasetId: 'public-global-port-visits-events:v3.0',
},
],
description: 'Por visit cluster events',
createdAt: '2024-05-16T08:21:11.723Z',
updatedAt: '2024-05-16T08:21:11.723Z',
category: DataviewCategory.Events,
},
]

export default dataviews
3 changes: 3 additions & 0 deletions apps/fishing-map/features/dataviews/dataviews.utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ import {
// used in workspaces with encounter events layers
export const ENCOUNTER_EVENTS_SOURCE_ID = 'encounter'
const ENCOUNTER_EVENTS_30MIN_SOURCE_ID = 'proto-global-encounters-events-30min'
export const PORT_VISITS_EVENTS_SOURCE_ID = 'port-visit-events'
export const LOITERING_EVENTS_SOURCE_ID = 'loitering-events'
export const VESSEL_GROUP_DATAVIEW_PREFIX = `vessel-group-`
export const BIG_QUERY_PREFIX = 'bq-'
const BIG_QUERY_4WINGS_PREFIX = `${BIG_QUERY_PREFIX}4wings-`
const BIG_QUERY_EVENTS_PREFIX = `${BIG_QUERY_PREFIX}events-`
Expand Down
Binary file modified apps/fishing-map/public/events-color-sprite.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions libs/api-types/src/dataviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ export type DataviewSublayerConfig = {
maxZoom?: number
}

export type FourwingsGeolocation = 'country' | 'port' | 'default'

export type ClusterMaxZoomLevelConfig = Partial<Record<FourwingsGeolocation, number>>

export interface DataviewConfig<Type = DataviewType> {
/** Type to define what kind of layer to render, ex: fourwings, context, draw... */
type?: Type
Expand Down Expand Up @@ -96,6 +100,7 @@ export interface DataviewConfig<Type = DataviewType> {
locale?: Locale
dynamicBreaks?: boolean
maxZoom?: number
clusterMaxZoomLevels?: ClusterMaxZoomLevelConfig
maxZoomCluster?: number
layers?: DataviewContexLayerConfig[]
/** Legacy for duplicated events in the API */
Expand Down
8 changes: 7 additions & 1 deletion libs/deck-layer-composer/src/resolvers/clusters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,11 @@ import {
getDataviewSqlFiltersResolved,
UrlDataviewInstance,
} from '@globalfishingwatch/dataviews-client'
import { FourwingsClustersLayerProps, getUTCDateTime } from '@globalfishingwatch/deck-layers'
import {
FourwingsClusterEventType,
FourwingsClustersLayerProps,
getUTCDateTime,
} from '@globalfishingwatch/deck-layers'
import { getDatasetsExtent, resolveEndpoint } from '@globalfishingwatch/datasets-client'
import { DataviewDatasetConfig, EndpointId } from '@globalfishingwatch/api-types'
import { DeckResolverFunction, ResolverGlobalConfig } from './types'
Expand Down Expand Up @@ -90,5 +94,7 @@ export const resolveDeckFourwingsClustersLayerProps: DeckResolverFunction<
endTime,
visible: dataview.config?.visible ?? true,
tilesUrl: resolveEndpoint(dataset, datasetConfig, { absolute: true }) || '',
clusterMaxZoomLevels: dataview.config?.clusterMaxZoomLevels,
eventType: dataset.subcategory as FourwingsClusterEventType,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { ScalePower, scaleSqrt } from 'd3-scale'
import { max } from 'simple-statistics'
import { GFWAPI, ParsedAPIError } from '@globalfishingwatch/api-client'
import { FourwingsClustersLoader } from '@globalfishingwatch/deck-loaders'
import { FourwingsGeolocation } from '@globalfishingwatch/api-types'
import { PATH_BASENAME } from '../../layers.config'
import {
DEFAULT_BACKGROUND_COLOR,
Expand All @@ -41,6 +42,8 @@ import {
FourwingsPointFeature,
} from './fourwings-clusters.types'

type ClusterMode = FourwingsGeolocation | 'positions'

type FourwingsClustersTileLayerState = {
error: string
clusterIndex: Supercluster
Expand All @@ -52,15 +55,26 @@ type FourwingsClustersTileLayerState = {

const defaultProps: DefaultProps<FourwingsClustersLayerProps> = {
tilesUrl: HEATMAP_API_TILES_URL,
clusterMaxZoomLevels: {
default: MAX_ZOOM_TO_CLUSTER_POINTS,
},
}

const ICON_SIZE = 16
const GEOLOCATION_PRIORITY: FourwingsGeolocation[] = ['country', 'port', 'default']

const ICON_SIZE: Record<FourwingsClusterEventType, number> = {
encounter: 16,
loitering: 16,
gap: 14,
port_visit: 12,
}
const MIN_CLUSTER_RADIUS = 12
const MAX_CLUSTER_RADIUS = 30
const ICON_MAPPING: Record<FourwingsClusterEventType, any> = {
encounter: { x: 0, y: 0, width: 36, height: 36 },
gap: { x: 40, y: 0, width: 36, height: 36, mask: true },
port_visit: { x: 80, y: 0, width: 36, height: 36 },
loitering: { x: 120, y: 0, width: 36, height: 36 },
}

const CLUSTER_LAYER_ID = 'clusters'
Expand All @@ -77,8 +91,19 @@ export class FourwingsClustersLayer extends CompositeLayer<
return super.isLoaded && this.state.viewportLoaded
}

get isInPositionsMode(): boolean {
return this.context.viewport.zoom > MAX_ZOOM_TO_CLUSTER_POINTS
get clusterMode(): ClusterMode {
const { clusterMaxZoomLevels } = this.props
let clusterMode: ClusterMode = 'positions'
for (const geolocation of GEOLOCATION_PRIORITY) {
if (
clusterMaxZoomLevels?.[geolocation] !== undefined &&
Math.floor(this.context.viewport.zoom) <= clusterMaxZoomLevels[geolocation]
) {
clusterMode = geolocation
break
}
}
return clusterMode
}

getError(): string {
Expand All @@ -92,7 +117,7 @@ export class FourwingsClustersLayer extends CompositeLayer<
viewportLoaded: false,
clusterIndex: new Supercluster({
radius: 70,
maxZoom: Math.floor(MAX_ZOOM_TO_CLUSTER_POINTS),
maxZoom: Math.floor(this.props.clusterMaxZoomLevels?.default || MAX_ZOOM_TO_CLUSTER_POINTS),
reduce: (accumulated, props) => {
accumulated.count += props.count
},
Expand Down Expand Up @@ -122,7 +147,7 @@ export class FourwingsClustersLayer extends CompositeLayer<
const object = {
...(info.object || ({} as FourwingsClusterFeature)),
id: info.object?.properties.id || `${(info.object?.geometry?.coordinates || []).join('-')}`,
...(this.isInPositionsMode &&
...(this.clusterMode === 'positions' &&
info.object?.properties.id && {
eventId: info.object?.properties.id,
}),
Expand All @@ -141,7 +166,7 @@ export class FourwingsClustersLayer extends CompositeLayer<

_onViewportLoad = (tiles: Tile2DHeader[]) => {
const { zoom } = this.context.viewport
if (this.isInPositionsMode) {
if (this.clusterMode === 'positions') {
const points = tiles.flatMap((tile) => {
return tile.content
? tile.content.map((feature: any) =>
Expand Down Expand Up @@ -286,11 +311,13 @@ export class FourwingsClustersLayer extends CompositeLayer<
return null
}
let url = getURLFromTemplate(tile.url!, tile)
if (this.isInPositionsMode) {
if (this.clusterMode === 'positions') {
url = url?.replace('{{type}}', 'position').concat(`&format=MVT`)
return this._fetchPositions(url!, { signal: tile.signal })
}
url = url?.replace('{{type}}', 'heatmap').concat(`&format=4WINGS&temporal-aggregation=true`)
url = url
?.replace('{{type}}', 'heatmap')
.concat(`&format=4WINGS&temporal-aggregation=true&geolocation=${this.clusterMode}`)
return this._fetchClusters(url!, { signal: tile.signal, tile })
}

Expand All @@ -313,15 +340,15 @@ export class FourwingsClustersLayer extends CompositeLayer<
}

filterSubLayer({ layer }: FilterContext) {
if (this.isInPositionsMode) {
if (this.clusterMode === 'positions') {
return !layer.id.includes(CLUSTER_LAYER_ID)
} else {
return true
}
}

renderLayers(): Layer<{}> | LayersList | null {
const { color, tilesUrl } = this.props
const { color, tilesUrl, eventType = 'encounter' } = this.props
const { clusters, points, radiusScale } = this.state
return [
new TileLayer<FourwingsPointFeature, any>(this.props, {
Expand All @@ -339,11 +366,11 @@ export class FourwingsClustersLayer extends CompositeLayer<
id: `${this.props.id}-${POINTS_LAYER_ID}-icons`,
data: points,
getPosition: this._getPosition,
getSize: ICON_SIZE,
getSize: ICON_SIZE[eventType],
sizeUnits: 'pixels',
iconAtlas: `${PATH_BASENAME}/events-color-sprite.png`,
iconMapping: ICON_MAPPING,
getIcon: () => 'encounter',
getIcon: () => eventType,
getPolygonOffset: (params: any) => getLayerGroupOffset(LayerGroup.Cluster, params),
pickable: true,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,25 @@
import { ClusterFeature, PointFeature } from 'supercluster'
import { PickingInfo } from '@deck.gl/core'
import { Tile2DHeader } from '@deck.gl/geo-layers/dist/tileset-2d'
import { EventTypes } from '@globalfishingwatch/api-types'
import { ClusterEventType } from 'libs/deck-layers/src/layers/cluster'
import { ClusterMaxZoomLevelConfig, EventTypes } from '@globalfishingwatch/api-types'
import { DeckLayerProps, DeckPickingObject } from '../../../types'

export type FourwingsClusterEventType =
| `${EventTypes.Encounter}`
| `${EventTypes.Gap}`
| `${EventTypes.Port}`
| `${EventTypes.Loitering}`

export type FourwingsClustersLayerProps = DeckLayerProps<{
startTime: number
endTime: number
color: string
datasetId: string
eventType?: FourwingsClusterEventType
maxClusterZoom?: number
tilesUrl: string
visible: boolean
clusterMaxZoomLevels?: ClusterMaxZoomLevelConfig
}>

export type FourwingsClusterProperties = {
Expand Down
Loading