Inference heavy mapping function (Typescript challenge!)

Struggling a little bit with getting proper inference working here. This seems to work well enough, but I need to be explicit about the type of my objects going into these functions. Passing in the raw object literal breaks TS. Any advice on making this a bit smarter?

import { lat, lon } from '~/types/coordinates' // branded types
import { EntityState } from '~/modules/simulation/entity-state.type'

export interface GeoJsonFeature<T extends WithLatLon<object>> {
  type: 'Feature'
  geometry: {
    type: 'Point' | 'LineString' | 'Polygon' | 'MultiPoint' | 'MultiLineString' | 'MultiPolygon'
    coordinates: [lon, lat]
  }
  properties: Omit<T, 'latitude' | 'longitude'>
}

type WithLatLon<T extends object> = {
  latitude: number
  longitude: number
} & T

/**
 * Convert an object that has latitude and longitude properties to a GeoJson with its other properties spread
 * to the properties object
 * @param data
 */
function toGeoJsonFeature<T extends object>(data: WithLatLon<T>): GeoJsonFeature<T> {
  const { latitude, longitude, ...propsWithoutLatLon } = data
  return {
    type: 'Feature',
    geometry: {
      type: 'Point',
      coordinates: [longitude as lon, latitude as lat]
    },
    properties: propsWithoutLatLon
  }
}

/**
 * Extract a GeoJson's coordinates and properties into a flattened object
 * @param feature
 */
function fromGeoJsonFeature<T extends WithLatLon<object>>(feature: GeoJsonFeature<T>): T {
  const { geometry, properties } = feature
  const [longitude, latitude] = geometry.coordinates

  return {
    ...properties,
    latitude: latitude as number,
    longitude: longitude as number
  } as T
}

const sampleEntityState: EntityState = {
  sourceType: 'exampleSourceType',
  id: '123',
  name: 'Sample Entity',
  latitude: 40.7128,
  longitude: -74.006,
  altitude: 30,
  azimuth: 180,
  speed: 60,
  time: '2024-02-07T12:00:00Z',
  symbol: 'sampleSymbol'
}

const sampleEntityStateGeoJson: GeoJsonFeature<EntityState> = {
  type: 'Feature',
  geometry: {
    type: 'Point',
    coordinates: [123 as lon, 345 as lat]
  },
  properties: {
    sourceType: 'exampleSourceType',
    id: '123',
    name: 'Sample Entity',
    altitude: 30,
    azimuth: 180,
    speed: 60,
    time: '2024-02-07T12:00:00Z',
    symbol: 'sampleSymbol'
  }
}

const geojson = toGeoJsonFeature(sampleEntityState) // Type: GeoJsonFeature<EntityState>

const entity = fromGeoJsonFeature(sampleEntityStateGeoJson) // Type: EntityState
Was this page helpful?