All files / core/systems/geohash system.ts

100% Statements 14/14
100% Branches 6/6
100% Functions 6/6
100% Lines 14/14

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51                15x 15x 15x             15x 2061x   15x         2061x 2061x               15x 1010x   1010x 1007x     1002x 1002x                
import { Option, pipe } from 'effect'
 
import { cellBox } from '../cell'
import { isMgrs } from '../mgrs/mgrs'
import { makeParser } from '../parser'
import type { Formatter, Parser } from '../types'
import { fromGeohash, isGeohash, toGeohash } from './geohash'
 
const LENGTH = 9
const PREFIX = /^(geohash|gh):\s*/i
const HAS_LETTER = /[a-z]/i
 
/**
 * The search reads a hash back only if no earlier parser claims it, so a hash made only of
 * digits, or one that is also an MGRS reference (`bjb125858` is south polar zone B, square JB),
 * is copied with the "gh:" prefix.
 */
const isAmbiguous = (hash: string): boolean =>
  !HAS_LETTER.test(hash) || isMgrs(hash)
 
export const formatter: Formatter = {
  id: 'Geohash',
  label: 'Geohash',
  maybeGridId: Option.none(),
  format: point => {
    const hash = toGeohash(point, LENGTH)
    return {
      lines: [hash, `${LENGTH} characters`],
      copyText: isAmbiguous(hash) ? `gh:${hash}` : hash,
    }
  },
}
 
/** Accepts "gh:u4pr", or a bare hash with at least one letter, so plain numbers stay with other parsers. */
export const parser: Parser = makeParser('Geohash', text =>
  pipe(
    text,
    Option.liftPredicate(input => PREFIX.test(input) || HAS_LETTER.test(input)),
    Option.map(input => input.replace(PREFIX, '')),
    Option.filter(isGeohash),
    Option.map(hash => {
      const cell = fromGeohash(hash)
      return {
        kind: 'Geohash',
        point: cell.center,
        maybeBounds: Option.some(cellBox(cell)),
      }
    }),
  ),
)