Skip to content

Commit

Permalink
wip UserTrackLayer
Browse files Browse the repository at this point in the history
  • Loading branch information
j8seangel committed Jul 24, 2024
1 parent a31e6ff commit ecabf5b
Show file tree
Hide file tree
Showing 8 changed files with 110 additions and 69 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,6 @@ import { ThinningLevels } from '@globalfishingwatch/api-client'
import { selectDebugOptions } from 'features/debug/debug.slice'
import { selectIsGuestUser } from 'features/user/selectors/user.selectors'

export {
setResource,
fetchResourceThunk,
selectResourceByUrl,
selectResources,
} from '@globalfishingwatch/dataviews-client'

const TRACK_THINNING_BY_ZOOM_GUEST = {
0: ThinningLevels.Insane,
4: ThinningLevels.Aggressive,
Expand Down
34 changes: 15 additions & 19 deletions apps/fishing-map/features/workspace/user/UserLayerPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,7 @@ import { Fragment, useState } from 'react'
import cx from 'classnames'
import { useSelector } from 'react-redux'
import { useTranslation } from 'react-i18next'
import {
DatasetStatus,
DatasetGeometryType,
ResourceStatus,
Dataset,
} from '@globalfishingwatch/api-types'
import { DatasetStatus, DatasetGeometryType, Dataset } from '@globalfishingwatch/api-types'
import { Tooltip, ColorBarOption, IconButton } from '@globalfishingwatch/ui-components'
import { UrlDataviewInstance } from '@globalfishingwatch/dataviews-client'
import {
Expand All @@ -16,7 +11,6 @@ import {
getUserDataviewDataset,
} from '@globalfishingwatch/datasets-client'
import { DrawFeatureType } from '@globalfishingwatch/deck-layers'
import { useDeckLayerLoadedState } from '@globalfishingwatch/deck-layer-composer'
import { useDebounce } from '@globalfishingwatch/react-hooks'
import styles from 'features/workspace/shared/LayerPanel.module.css'
import { useDataviewInstancesConnect } from 'features/workspace/workspace.hook'
Expand Down Expand Up @@ -48,7 +42,7 @@ import InfoModal from '../common/InfoModal'
import ExpandedContainer from '../shared/ExpandedContainer'
import DatasetSchemaField from '../shared/DatasetSchemaField'
import { showSchemaFilter } from '../common/LayerSchemaFilter'
import UserLayerTrackPanel, { useUserLayerTrackResource } from './UserLayerTrackPanel'
import UserLayerTrackPanel, { useUserLayerTrackMetadata } from './UserLayerTrackPanel'

type UserPanelProps = {
dataview: UrlDataviewInstance
Expand All @@ -66,12 +60,12 @@ function UserPanel({ dataview, onToggle }: UserPanelProps): React.ReactElement {
const userId = useSelector(selectUserId)
const guestUser = useSelector(selectIsGuestUser)
const layerActive = dataview?.config?.visible ?? true
const layerLoaded = useDeckLayerLoadedState()[dataview.id]?.loaded
const layerLoadedDebounced = useDebounce(layerLoaded, 300)
const dataset = getUserDataviewDataset(dataview)
const datasetGeometryType = getDatasetGeometryType(dataset)
const { resource, featuresColoredByField } = useUserLayerTrackResource(dataview)
const trackError = resource?.status === ResourceStatus.Error
const { loaded, hasFeaturesColoredByField, error } = useUserLayerTrackMetadata(dataview)
const layerLoaded = loaded && !error
const layerLoadedDebounced = useDebounce(layerLoaded, 300)
const layerLoading = layerActive && !layerLoadedDebounced && !error

useAutoRefreshImportingDataset(layerActive ? dataset : ({} as Dataset), 5000)

Expand Down Expand Up @@ -180,7 +174,7 @@ function UserPanel({ dataview, onToggle }: UserPanelProps): React.ReactElement {
className={styles.switch}
dataview={dataview}
onToggle={onToggle}
color={featuresColoredByField ? COLOR_SECONDARY_BLUE : undefined}
color={hasFeaturesColoredByField ? COLOR_SECONDARY_BLUE : undefined}
testId={`context-layer-${dataview.id}`}
/>
{ONLY_GFW_STAFF_DATAVIEW_SLUGS.includes(dataview.dataviewId as string) && (
Expand Down Expand Up @@ -218,7 +212,7 @@ function UserPanel({ dataview, onToggle }: UserPanelProps): React.ReactElement {
<Color
dataview={dataview}
open={colorOpen}
disabled={featuresColoredByField}
disabled={hasFeaturesColoredByField}
onColorClick={changeColor}
onToggleClick={onToggleColorOpen}
onClickOutside={closeExpandedContainer}
Expand Down Expand Up @@ -253,21 +247,23 @@ function UserPanel({ dataview, onToggle }: UserPanelProps): React.ReactElement {
</ExpandedContainer>
)}
{<InfoModal dataview={dataview} />}
<Remove dataview={dataview} loading={layerActive && !layerLoaded} />
<Remove dataview={dataview} loading={layerLoading} />
{items.length > 1 && (
<IconButton
size="small"
ref={setActivatorNodeRef}
{...listeners}
icon="drag"
icon={error ? 'warning' : 'drag'}
type={error ? 'warning' : 'default'}
tooltip={error ? error : ''}
className={styles.dragger}
/>
)}
</div>
<IconButton
icon={layerActive ? 'more' : undefined}
type="default"
loading={layerActive && !layerLoadedDebounced}
icon={layerActive ? (error ? 'warning' : 'more') : undefined}
type={error ? 'warning' : 'default'}
loading={layerLoading}
className={cx('print-hidden', styles.shownUntilHovered)}
size="small"
/>
Expand Down
40 changes: 21 additions & 19 deletions apps/fishing-map/features/workspace/user/UserLayerTrackPanel.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,16 @@
import { Fragment, useCallback, useState } from 'react'
import { Fragment, useCallback, useMemo, useState } from 'react'
import cx from 'classnames'
import { useSelector } from 'react-redux'
import { FeatureCollection } from 'geojson'
import { useTranslation } from 'react-i18next'
import { uniqBy } from 'es-toolkit'
import { NO_RECORD_ID } from '@globalfishingwatch/data-transforms'
import { DatasetTypes, Resource } from '@globalfishingwatch/api-types'
import {
UrlDataviewInstance,
resolveDataviewDatasetResource,
selectResourceByUrl,
} from '@globalfishingwatch/dataviews-client'
import { UrlDataviewInstance } from '@globalfishingwatch/dataviews-client'
import {
getUserDataviewDataset,
getDatasetConfigurationProperty,
} from '@globalfishingwatch/datasets-client'
import { useGetDeckLayer } from '@globalfishingwatch/deck-layer-composer'
import { UserTracksLayer } from '@globalfishingwatch/deck-layers'
import styles from 'features/workspace/shared/LayerPanel.module.css'
import { selectActiveTrackDataviews } from 'features/dataviews/selectors/dataviews.instances.selectors'

Expand All @@ -24,40 +20,46 @@ type UserPanelProps = {

const SEE_MORE_LENGTH = 5

export function useUserLayerTrackResource(dataview: UrlDataviewInstance) {
export function useUserLayerTrackMetadata(dataview: UrlDataviewInstance) {
const dataset = getUserDataviewDataset(dataview)
const allTracksActive = useSelector(selectActiveTrackDataviews)
const { url: trackUrl } = resolveDataviewDatasetResource(dataview, DatasetTypes.UserTracks)
const resource: Resource<FeatureCollection> = useSelector(
selectResourceByUrl<FeatureCollection>(trackUrl)
)
const trackLayer = useGetDeckLayer<UserTracksLayer>(dataview?.id)
const data = useMemo(() => {
return trackLayer?.instance?.getData()
}, [trackLayer])

const idProperty = getDatasetConfigurationProperty({
dataset,
property: 'lineId',
}) as string

const hasRecordIds = idProperty
? resource?.data?.features?.some((f) => f.properties?.id !== NO_RECORD_ID)
? data?.features?.some((f) => f.properties?.id !== NO_RECORD_ID)
: false

const singleTrack = allTracksActive.length === 1
const featuresColoredByField = singleTrack && resource?.data && hasRecordIds
const hasFeaturesColoredByField = singleTrack && data && hasRecordIds

return { resource, featuresColoredByField, hasRecordIds }
return {
data,
hasRecordIds,
hasFeaturesColoredByField,
error: trackLayer?.instance?.getError(),
loaded: trackLayer?.loaded,
}
}

function UserLayerTrackPanel({ dataview }: UserPanelProps) {
const { t } = useTranslation()
const [seeMoreOpen, setSeeMoreOpen] = useState(false)

const { resource, featuresColoredByField } = useUserLayerTrackResource(dataview)
const { data, hasFeaturesColoredByField } = useUserLayerTrackMetadata(dataview)

const onSeeMoreClick = useCallback(() => {
setSeeMoreOpen(!seeMoreOpen)
}, [seeMoreOpen])

if (!featuresColoredByField || !resource?.data?.features) {
if (!hasFeaturesColoredByField || !data?.features) {
return null
}

Expand All @@ -68,7 +70,7 @@ function UserLayerTrackPanel({ dataview }: UserPanelProps) {
}) as string
const filterValues = dataview.config?.filters?.[lineIdProperty] || []

const features = uniqBy(resource.data?.features, (f) => f.properties?.[lineIdProperty])
const features = uniqBy(data?.features, (f) => f.properties?.[lineIdProperty])

return (
<Fragment>
Expand Down
39 changes: 34 additions & 5 deletions libs/deck-layers/src/layers/user/UserTracksLayer.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { CompositeLayer, DefaultProps, Layer, LayerProps } from '@deck.gl/core'
import { PathLayer, PathLayerProps } from '@deck.gl/layers'
import { parse } from '@loaders.gl/core'
import { UserTrackLoader } from '@globalfishingwatch/deck-loaders'
import { UserTrackLoader, UserTrackRawData } from '@globalfishingwatch/deck-loaders'
import { GFWAPI } from '@globalfishingwatch/api-client'
import { DEFAULT_HIGHLIGHT_COLOR_VEC } from '../vessel/vessel.config'
import { hexToDeckColor } from '../../utils'
Expand All @@ -25,7 +25,7 @@ export class UserTracksPathLayer<DataT = any, ExtraProps = {}> extends PathLayer
DataT,
_UserTrackLayerProps<DataT> & ExtraProps
> {
static layerName = 'UserTracksLayer'
static layerName = 'UserTracksPathLayer'
static defaultProps = defaultProps

getShaders() {
Expand Down Expand Up @@ -104,9 +104,19 @@ export class UserTracksPathLayer<DataT = any, ExtraProps = {}> extends PathLayer
}
}

type UserTracksLayerState = {
error: string
}

export class UserTracksLayer extends CompositeLayer<LayerProps & UserTrackLayerProps> {
static layerName = 'UserTracksLayer'
static defaultProps = defaultProps
rawData!: UserTrackRawData
state!: UserTracksLayerState

get isLoaded(): boolean {
return !this.state?.error && super.isLoaded
}

_fetch = async (
url: string,
Expand All @@ -133,7 +143,23 @@ export class UserTracksLayer extends CompositeLayer<LayerProps & UserTrackLayerP
filters: this.props.filters,
},
}
return await parse(response, UserTrackLoader, userTracksLoadOptions)
const { data, binary } = await parse(response, UserTrackLoader, userTracksLoadOptions)
this.rawData = data
return binary
}

_onLayerError = (error: Error) => {
console.warn(error.message)
this.setState({ error: error.message })
return true
}

getError() {
return this.state.error
}

getData() {
return this.rawData
}

renderLayers() {
Expand All @@ -142,18 +168,21 @@ export class UserTracksLayer extends CompositeLayer<LayerProps & UserTrackLayerP
const tilesUrl = new URL(layer.tilesUrl)
tilesUrl.searchParams.set('filters', Object.values(filters || {}).join(','))
return new UserTracksPathLayer<any>({
...(this.props as any),
id: layer.id,
data: tilesUrl.toString(),
_pathType: 'open',
fetch: this._fetch,
widthUnits: 'pixels',
widthScale: 1,
// updateTriggers: {
// getColor: [color],
// },
onError: this._onLayerError,
wrapLongitude: true,
jointRounded: true,
capRounded: true,
widthMinPixels: 1,
width: 1,
getWidth: 1,
getColor: hexToDeckColor(color),
})
})
Expand Down
42 changes: 27 additions & 15 deletions libs/deck-loaders/src/user/lib/parse-user-tracks.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { UserTrack } from '@globalfishingwatch/api-types'
import { TrackCoordinatesPropertyFilter, filterTrackByCoordinateProperties } from './utils'
import { UserTrackData } from './types'
import { UserTrackBinaryData, UserTrackData } from './types'

function isNumeric(str: string | number) {
if (!str) return false
Expand Down Expand Up @@ -46,24 +46,29 @@ export const parseUserTrack = (
arrayBuffer: ArrayBuffer,
params = {} as ParseUserTrackParams
): UserTrackData => {
const data = arrayBufferToJson(arrayBuffer)
if (!data) {
const rawData = arrayBufferToJson(arrayBuffer)
if (!rawData) {
return {} as UserTrackData
}

const filteredTrack = filterTrackByCoordinateProperties(data, {
const coordinatesFilter = timestampProperty
? { ...params.filters, [timestampProperty]: [-Infinity, Infinity] }
: params.filters
const data = filterTrackByCoordinateProperties(rawData, {
filters: getCoordinatesFilter(params.filters),
includeNonTemporalFeatures: true,
includeCoordinateProperties: [timestampProperty],
})

const length = filteredTrack.features.reduce((acc, feature) => {
console.log('🚀 ~ data:', data)
const length = data.features.reduce((acc, feature) => {
if (feature.geometry.type === 'MultiLineString') {
return acc + feature.geometry.coordinates.length
}
return acc + 1
}, 0)

const startIndices = filteredTrack.features.reduce(
const startIndices = data.features.reduce(
(acc, feature) => {
const lastIndex = acc[acc.length - 1]
if (feature.geometry.type === 'MultiLineString') {
Expand All @@ -80,28 +85,35 @@ export const parseUserTrack = (
[0]
)

const track = {
const binary = {
length,
startIndices,
attributes: {
getPath: {
value: new Float32Array(
filteredTrack.features.flatMap((f) => f.geometry.coordinates.flatMap((l) => l.flat()))
data.features.flatMap((f) => f.geometry.coordinates.flatMap((l) => l.flat()))
),
size: 2,
},
getTimestamp: {
value: new Float32Array(
filteredTrack.features.flatMap(
(f) =>
f.properties?.coordinateProperties?.[timestampProperty] ||
Array(f.geometry.coordinates.length).fill(0)
)
data.features.flatMap((f) => {
const timestampsLength =
f.geometry.type === 'MultiLineString'
? f.geometry.coordinates.reduce((acc, next) => {
return acc + next.length
}, 0)
: f.geometry.coordinates.length
return f.geometry.type === 'MultiLineString'
? f.properties?.coordinateProperties?.[timestampProperty]?.flat()
: f.properties?.coordinateProperties?.[timestampProperty] ||
Array(timestampsLength).fill(0)
})
),
size: 1,
},
},
} as UserTrackData
} as UserTrackBinaryData

return track
return { data, binary }
}
11 changes: 10 additions & 1 deletion libs/deck-loaders/src/user/lib/types.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
export type UserTrackData = {
import { FeatureCollection, LineString, MultiLineString } from 'geojson'

export type UserTrackBinaryData = {
// Number of geometries
length: number
// Indices into positions where each path starts
Expand All @@ -11,3 +13,10 @@ export type UserTrackData = {
getTimestamp: { value: Float32Array; size: number }
}
}

export type UserTrackRawData = FeatureCollection<LineString | MultiLineString>

export type UserTrackData = {
data: UserTrackRawData
binary: UserTrackBinaryData
}
Loading

0 comments on commit ecabf5b

Please sign in to comment.