Skip to content

Commit

Permalink
add dataview mocks and different clustering techniques
Browse files Browse the repository at this point in the history
  • Loading branch information
satellitestudiodesign committed Sep 20, 2024
1 parent 9c3bff1 commit a85a9a9
Show file tree
Hide file tree
Showing 9 changed files with 137 additions and 16 deletions.
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
59 changes: 58 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,8 @@ const dataviews: Dataview[] = [
config: {
type: 'FOURWINGS_TILE_CLUSTER',
color: '#FAE9A0',
icon: 'encounter',
maxZoomCluster: 6,
},
datasetsConfig: [
{
Expand All @@ -26,6 +32,57 @@ 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',
icon: 'loitering',
maxZoomCluster: 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',
icon: 'port_visit',
maxCountryZoomCluster: 4,
maxZoomCluster: 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.
2 changes: 2 additions & 0 deletions libs/api-types/src/dataviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,9 @@ export interface DataviewConfig<Type = DataviewType> {
locale?: Locale
dynamicBreaks?: boolean
maxZoom?: number
maxCountryZoomCluster?: number
maxZoomCluster?: number
icon?: string
layers?: DataviewContexLayerConfig[]
/** Legacy for duplicated events in the API */
duplicatedEventsWorkaround?: boolean
Expand Down
9 changes: 8 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 {
ClusterEventType,
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,8 @@ export const resolveDeckFourwingsClustersLayerProps: DeckResolverFunction<
endTime,
visible: dataview.config?.visible ?? true,
tilesUrl: resolveEndpoint(dataset, datasetConfig, { absolute: true }) || '',
maxCountryClusterZoom: dataview.config?.maxCountryZoomCluster,
maxProximityClusterZoom: dataview.config?.maxZoomCluster,
icon: dataview.config?.icon as ClusterEventType,
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ import {
FourwingsPointFeature,
} from './fourwings-clusters.types'

type ClusterMode = 'positions' | 'country' | 'proximity'

type FourwingsClustersTileLayerState = {
error: string
clusterIndex: Supercluster
Expand All @@ -54,13 +56,19 @@ const defaultProps: DefaultProps<FourwingsClustersLayerProps> = {
tilesUrl: HEATMAP_API_TILES_URL,
}

const ICON_SIZE = 16
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 +85,25 @@ 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 {
if (
this.props.maxCountryClusterZoom !== undefined &&
this.context.viewport.zoom <= this.props.maxCountryClusterZoom + 0.5
) {
console.log('country')

return 'country'
}
if (
this.props.maxProximityClusterZoom !== undefined &&
this.context.viewport.zoom <=
(this.props.maxProximityClusterZoom || MAX_ZOOM_TO_CLUSTER_POINTS) + 0.5
) {
console.log('proximity')
return 'proximity'
}
console.log('positions')
return 'positions'
}

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.maxProximityClusterZoom || 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 @@ -290,8 +315,9 @@ export class FourwingsClustersLayer extends CompositeLayer<
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`)
return this._fetchClusters(url!, { signal: tile.signal, tile })
// this.clusterMode === 'positions'
url = url?.replace('{{type}}', 'position').concat(`&format=MVT`)
return this._fetchPositions(url!, { signal: tile.signal })
}

_getPosition = (d: FourwingsClusterFeature) => {
Expand All @@ -313,15 +339,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, icon = 'encounter' } = this.props
const { clusters, points, radiusScale } = this.state
return [
new TileLayer<FourwingsPointFeature, any>(this.props, {
Expand All @@ -339,11 +365,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[icon],
sizeUnits: 'pixels',
iconAtlas: `${PATH_BASENAME}/events-color-sprite.png`,
iconMapping: ICON_MAPPING,
getIcon: () => 'encounter',
getIcon: () => icon,
getPolygonOffset: (params: any) => getLayerGroupOffset(LayerGroup.Cluster, params),
pickable: true,
}),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import { ClusterFeature, PointFeature } from 'supercluster'
import { PickingInfo } from '@deck.gl/core'
import { Tile2DHeader } from '@deck.gl/geo-layers/dist/tileset-2d'
import { ClusterEventType } from 'libs/deck-layers/src/layers/cluster'
import { 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
icon?: ClusterEventType
maxCountryClusterZoom?: number
maxProximityClusterZoom?: number
}>

export type FourwingsClusterProperties = {
Expand Down

0 comments on commit a85a9a9

Please sign in to comment.