//
// Copyright (c) 2012, SkyFoundry LLC
// All Rights Reserved
//
// History:
// 19 Oct 12 Brian Frank Creation
//
**
** Geographic coordinate as latitude and longitute in decimal degrees.
**
@Js
@Serializable { simple = true }
const class Coord
{
//////////////////////////////////////////////////////////////////////////
// Construction
//////////////////////////////////////////////////////////////////////////
** Default value is "C(0.0,0.0)"
const static Coord defVal := Coord(0, 0)
** Decode from string formatted as "C(lat,lng)"
static new fromStr(Str s, Bool checked := true)
{
try
{
if (!s.startsWith("C(") || !s.endsWith(")")) throw Err()
comma := s.index(",", 3)
return make(s[2..<comma].toFloat, s[comma+1..<-1].toFloat)
}
catch (Err e) {}
if (checked) throw ParseErr("Coor: $s")
return null
}
** Construct from floating point decimal degrees
new make(Float lat, Float lng)
{
// store as micro-degrees
this.ulat = (lat * 1_000_000f).toInt
this.ulng = (lng * 1_000_000f).toInt
if (ulat < -90_000_000 || ulat > 90_000_000) throw ArgErr("Invalid lat > +/- 90")
if (ulng < -180_000_000 || ulng > 180_000_000) throw ArgErr("Invalid lng > +/- 180")
}
** Construct from decimal micro-degrees
@NoDoc static Coord makeu(Int ulat, Int ulng) { makeuImpl(ulat, ulng) }
private new makeuImpl(Int ulat, Int ulng)
{
this.ulat = ulat
this.ulng = ulng
if (ulat < -90_000_000 || ulat > 90_000_000) throw ArgErr("Invalid lat > +/- 90")
if (ulng < -180_000_000 || ulng > 180_000_000) throw ArgErr("Invalid lng > +/- 180")
}
//////////////////////////////////////////////////////////////////////////
// Access
//////////////////////////////////////////////////////////////////////////
** Latitude in decimal degrees
Float lat() { ulat.toFloat / 1_000_000f }
** Longtitude in decimal degrees
Float lng() { ulng.toFloat / 1_000_000f }
** Latitude in micro-degrees
@NoDoc const Int ulat
** Longitude in micro-degrees
@NoDoc const Int ulng
** Hash is based on lat/lng
override Int hash() { pack }
** Equality is based on lat/lng
override Bool equals(Obj? that)
{
x := that as Coord
if (x == null) return false
return ulat == x.ulat && ulng == x.ulng
}
** Represet as "C(lat,lng)"
override Str toStr()
{
s := StrBuf()
s.add("C(")
uToStr(s, ulat)
s.addChar(',')
uToStr(s, ulng)
s.add(")")
return s.toStr
}
private Void uToStr(StrBuf s, Int ud)
{
if (ud < 0) { s.addChar('-'); ud = -ud }
if (ud < 1_000_000)
{
s.add((ud.toFloat / 1_000_000f).toLocale("0.0#####"))
return
}
x := ud.toStr
dot := x.size - 6
end := x.size
while (end > dot+1 && x[end-1] == '0') --end
for (i:=0; i<dot; ++i) s.addChar(x[i])
s.addChar('.')
for (i:=dot; i<end; ++i) s.addChar(x[i])
}
//////////////////////////////////////////////////////////////////////////
// 64-Bit Int Packing
//////////////////////////////////////////////////////////////////////////
**
** Pack into a 64-bit integer which is encoded as:
** lat+90 in micro-degrees << 32-bits | lng+180 in micro-degrees
**
@NoDoc Int pack()
{
(ulat + 90_000_000).and(0xfff_ffff).shiftl(32).or((ulng+180_000_000).and(0xffff_ffff))
}
** Unpack froma 64-bit integer - see `pack`
@NoDoc static Coord unpack(Int bits)
{
makeu(bits.shiftr(32).and(0xfff_ffff) - 90_000_000,
bits.and(0xffff_ffff) - 180_000_000)
}
//////////////////////////////////////////////////////////////////////////
// Math
//////////////////////////////////////////////////////////////////////////
** Compute great-circle distance two coordinates using haversine forumula.
Float dist(Coord c2)
{
c1 := this
r := 6371 // km
dLat := (c2.lat - c1.lat).toRadians.div(2f).sin
dLng := (c2.lng - c1.lng).toRadians.div(2f).sin
lat1 := c1.lat.toRadians.cos
lat2 := c2.lat.toRadians.cos
a := (dLat * dLat) + (dLng * dLng * lat1 * lat2)
c := 2f * Float.atan2(a.sqrt, (1f-a).sqrt)
d := r * c;
return d * 1000f
}
}