1package geojson
2
3import (
4	"encoding/binary"
5	"math"
6	"strconv"
7	"unsafe"
8
9	"github.com/tidwall/gjson"
10	"github.com/tidwall/tile38/geojson/geo"
11	"github.com/tidwall/tile38/geojson/poly"
12)
13
14const sizeofPosition = 24 // (X,Y,Z) * 8
15
16// Position is a simple point
17type Position poly.Point
18
19func pointPositions(positions []Position) []poly.Point {
20	return *(*[]poly.Point)(unsafe.Pointer(&positions))
21}
22func polyPositions(positions []Position) poly.Polygon {
23	return *(*poly.Polygon)(unsafe.Pointer(&positions))
24}
25func polyMultiPositions(positions [][]Position) []poly.Polygon {
26	return *(*[]poly.Polygon)(unsafe.Pointer(&positions))
27}
28func polyExteriorHoles(positions [][]Position) (exterior poly.Polygon, holes []poly.Polygon) {
29	switch len(positions) {
30	case 0:
31	case 1:
32		exterior = polyPositions(positions[0])
33	default:
34		exterior = polyPositions(positions[0])
35		holes = polyMultiPositions(positions[1:])
36	}
37	return
38}
39
40func appendPositionJSON(json []byte, p Position, isCordZ bool) []byte {
41	json = strconv.AppendFloat(json, p.X, 'f', -1, 64)
42	json = append(json, ',')
43	json = strconv.AppendFloat(json, p.Y, 'f', -1, 64)
44	if isCordZ {
45		json = append(json, ',')
46		json = strconv.AppendFloat(json, p.Z, 'f', -1, 64)
47	}
48	return json
49}
50
51const earthRadius = 6371e3
52
53func toRadians(deg float64) float64 { return deg * math.Pi / 180 }
54func toDegrees(rad float64) float64 { return rad * 180 / math.Pi }
55
56// DistanceTo calculates the distance to a position
57func (p Position) DistanceTo(position Position) float64 {
58	return geo.DistanceTo(p.Y, p.X, position.Y, position.X)
59}
60
61// Destination calculates a new position based on the distance and bearing.
62func (p Position) Destination(meters, bearingDegrees float64) Position {
63	lat, lon := geo.DestinationPoint(p.Y, p.X, meters, bearingDegrees)
64	return Position{X: lon, Y: lat, Z: 0}
65}
66
67func fillPosition(coords gjson.Result) (Position, error) {
68	var p Position
69	v := coords.Array()
70	switch len(v) {
71	case 0:
72		return p, errInvalidNumberOfPositionValues
73	case 1:
74		if v[0].Type != gjson.Number {
75			return p, errInvalidPositionValue
76		}
77		return p, errInvalidNumberOfPositionValues
78	}
79	for i := 0; i < len(v); i++ {
80		if v[i].Type != gjson.Number {
81			return p, errInvalidPositionValue
82		}
83	}
84	p.X = v[0].Float()
85	p.Y = v[1].Float()
86	if len(v) > 2 {
87		p.Z = v[2].Float()
88	} else {
89		p.Z = nilz
90	}
91	return p, nil
92}
93
94func fillPositionBytes(b []byte, isCordZ bool) (Position, []byte, error) {
95	var p Position
96	if len(b) < 8 {
97		return p, b, errNotEnoughData
98	}
99	p.X = math.Float64frombits(binary.LittleEndian.Uint64(b))
100	b = b[8:]
101	if len(b) < 8 {
102		return p, b, errNotEnoughData
103	}
104	p.Y = math.Float64frombits(binary.LittleEndian.Uint64(b))
105	b = b[8:]
106	if isCordZ {
107		if len(b) < 8 {
108			return p, b, errNotEnoughData
109		}
110		p.Z = math.Float64frombits(binary.LittleEndian.Uint64(b))
111		b = b[8:]
112	} else {
113		p.Z = nilz
114	}
115	return p, b, nil
116}
117
118// ExternalJSON is the simple json representation of the position used for external applications.
119func (p Position) ExternalJSON() string {
120	if p.Z != 0 {
121		return `{"lat":` + strconv.FormatFloat(p.Y, 'f', -1, 64) + `,"lon":` + strconv.FormatFloat(p.X, 'f', -1, 64) + `,"z":` + strconv.FormatFloat(p.Z, 'f', -1, 64) + `}`
122	}
123	return `{"lat":` + strconv.FormatFloat(p.Y, 'f', -1, 64) + `,"lon":` + strconv.FormatFloat(p.X, 'f', -1, 64) + `}`
124}
125