mirror of https://github.com/tidwall/tile38.git
557 lines
12 KiB
Go
557 lines
12 KiB
Go
|
package geojson
|
||
|
|
||
|
import (
|
||
|
"bytes"
|
||
|
"encoding/binary"
|
||
|
)
|
||
|
|
||
|
////////////////////////////////
|
||
|
// level 1
|
||
|
////////////////////////////////
|
||
|
|
||
|
func fillLevel1Map(m map[string]interface{}) (
|
||
|
coordinates Position, bbox *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
switch v := m["coordinates"].(type) {
|
||
|
default:
|
||
|
err = errInvalidCoordinates
|
||
|
return
|
||
|
case nil:
|
||
|
err = errCoordinatesRequired
|
||
|
return
|
||
|
case []interface{}:
|
||
|
coordinates, err = fillPosition(v)
|
||
|
}
|
||
|
if err == nil {
|
||
|
bbox, err = fillBBox(m)
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func fillLevel1Bytes(b []byte, bbox *BBox, isCordZ bool) (
|
||
|
coordinates Position, bboxOut *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
bboxOut = bbox
|
||
|
coordinates, bytesOut, err = fillPositionBytes(b, isCordZ)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func level1CalculatedBBox(coordinates Position, bbox *BBox) BBox {
|
||
|
if bbox != nil {
|
||
|
return *bbox
|
||
|
}
|
||
|
return BBox{
|
||
|
Min: coordinates,
|
||
|
Max: coordinates,
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func level1PositionCount(coordinates Position, bbox *BBox) int {
|
||
|
if bbox != nil {
|
||
|
return 3
|
||
|
}
|
||
|
return 1
|
||
|
}
|
||
|
|
||
|
func level1Weight(coordinates Position, bbox *BBox) int {
|
||
|
return level1PositionCount(coordinates, bbox) * sizeofPosition
|
||
|
}
|
||
|
|
||
|
func level1JSON(name string, coordinates Position, bbox *BBox) string {
|
||
|
isCordZ := level1IsCoordZDefined(coordinates, bbox)
|
||
|
var buf bytes.Buffer
|
||
|
buf.WriteString(`{"type":"`)
|
||
|
buf.WriteString(name)
|
||
|
buf.WriteString(`","coordinates":[`)
|
||
|
coordinates.writeJSON(&buf, isCordZ)
|
||
|
buf.WriteByte(']')
|
||
|
bbox.write(&buf)
|
||
|
buf.WriteByte('}')
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func level1IsCoordZDefined(coordinates Position, bbox *BBox) bool {
|
||
|
if bbox.isCordZDefined() {
|
||
|
return true
|
||
|
}
|
||
|
return coordinates.Z != nilz
|
||
|
}
|
||
|
|
||
|
func level1Bytes(objType byte, coordinates Position, bbox *BBox) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
isCordZ := level1IsCoordZDefined(coordinates, bbox)
|
||
|
writeHeader(&buf, objType, bbox, isCordZ)
|
||
|
coordinates.writeBytes(&buf, isCordZ)
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
// level 2
|
||
|
////////////////////////////////
|
||
|
|
||
|
func fillLevel2Map(m map[string]interface{}) (
|
||
|
coordinates []Position, bbox *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
switch v := m["coordinates"].(type) {
|
||
|
default:
|
||
|
err = errInvalidCoordinates
|
||
|
return
|
||
|
case nil:
|
||
|
err = errCoordinatesRequired
|
||
|
return
|
||
|
case []interface{}:
|
||
|
coordinates = make([]Position, len(v))
|
||
|
for i, v := range v {
|
||
|
v, ok := v.([]interface{})
|
||
|
if !ok {
|
||
|
err = errCoordinatesMustBeArray
|
||
|
return
|
||
|
}
|
||
|
var p Position
|
||
|
p, err = fillPosition(v)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
coordinates[i] = p
|
||
|
}
|
||
|
}
|
||
|
bbox, err = fillBBox(m)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func fillLevel2Bytes(b []byte, bbox *BBox, isCordZ bool) (
|
||
|
coordinates []Position, bboxOut *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
bboxOut = bbox
|
||
|
if len(b) < 4 {
|
||
|
err = errNotEnoughData
|
||
|
return
|
||
|
}
|
||
|
coordinates = make([]Position, int(binary.LittleEndian.Uint32(b)))
|
||
|
b = b[4:]
|
||
|
for i := 0; i < len(coordinates); i++ {
|
||
|
coordinates[i], b, err = fillPositionBytes(b, isCordZ)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
bytesOut = b
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func level2CalculatedBBox(coordinates []Position, bbox *BBox) BBox {
|
||
|
if bbox != nil {
|
||
|
return *bbox
|
||
|
}
|
||
|
_, bbox2 := positionBBox(0, BBox{}, coordinates)
|
||
|
return bbox2
|
||
|
}
|
||
|
|
||
|
func level2PositionCount(coordinates []Position, bbox *BBox) int {
|
||
|
if bbox != nil {
|
||
|
return 2 + len(coordinates)
|
||
|
}
|
||
|
return len(coordinates)
|
||
|
}
|
||
|
|
||
|
func level2Weight(coordinates []Position, bbox *BBox) int {
|
||
|
return level2PositionCount(coordinates, bbox) * sizeofPosition
|
||
|
}
|
||
|
|
||
|
func level2JSON(name string, coordinates []Position, bbox *BBox) string {
|
||
|
isCordZ := level2IsCoordZDefined(coordinates, bbox)
|
||
|
var buf bytes.Buffer
|
||
|
buf.WriteString(`{"type":"`)
|
||
|
buf.WriteString(name)
|
||
|
buf.WriteString(`","coordinates":[`)
|
||
|
for i, p := range coordinates {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(',')
|
||
|
}
|
||
|
buf.WriteByte('[')
|
||
|
p.writeJSON(&buf, isCordZ)
|
||
|
buf.WriteByte(']')
|
||
|
}
|
||
|
buf.WriteByte(']')
|
||
|
bbox.write(&buf)
|
||
|
buf.WriteByte('}')
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func level2IsCoordZDefined(coordinates []Position, bbox *BBox) bool {
|
||
|
if bbox.isCordZDefined() {
|
||
|
return true
|
||
|
}
|
||
|
for _, p := range coordinates {
|
||
|
if p.Z != nilz {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func level2Bytes(objType byte, coordinates []Position, bbox *BBox) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
isCordZ := level2IsCoordZDefined(coordinates, bbox)
|
||
|
writeHeader(&buf, objType, bbox, isCordZ)
|
||
|
b := make([]byte, 4)
|
||
|
binary.LittleEndian.PutUint32(b, uint32(len(coordinates)))
|
||
|
buf.Write(b)
|
||
|
for _, p := range coordinates {
|
||
|
p.writeBytes(&buf, isCordZ)
|
||
|
}
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
// level 3
|
||
|
////////////////////////////////
|
||
|
|
||
|
func fillLevel3Map(m map[string]interface{}) (
|
||
|
coordinates [][]Position, bbox *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
switch v := m["coordinates"].(type) {
|
||
|
default:
|
||
|
err = errInvalidCoordinates
|
||
|
return
|
||
|
case nil:
|
||
|
err = errCoordinatesRequired
|
||
|
return
|
||
|
case []interface{}:
|
||
|
coordinates = make([][]Position, len(v))
|
||
|
for i, v := range v {
|
||
|
v, ok := v.([]interface{})
|
||
|
if !ok {
|
||
|
err = errInvalidCoordinatesValue
|
||
|
return
|
||
|
}
|
||
|
ps := make([]Position, len(v))
|
||
|
for i, v := range v {
|
||
|
v, ok := v.([]interface{})
|
||
|
if !ok {
|
||
|
err = errInvalidCoordinatesValue
|
||
|
return
|
||
|
}
|
||
|
var p Position
|
||
|
p, err = fillPosition(v)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
ps[i] = p
|
||
|
}
|
||
|
coordinates[i] = ps
|
||
|
}
|
||
|
}
|
||
|
bbox, err = fillBBox(m)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func fillLevel3Bytes(b []byte, bbox *BBox, isCordZ bool) (
|
||
|
coordinates [][]Position, bboxOut *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
bboxOut = bbox
|
||
|
if len(b) < 4 {
|
||
|
err = errNotEnoughData
|
||
|
return
|
||
|
}
|
||
|
coordinates = make([][]Position, int(binary.LittleEndian.Uint32(b)))
|
||
|
b = b[4:]
|
||
|
for i := 0; i < len(coordinates); i++ {
|
||
|
if len(b) < 4 {
|
||
|
err = errNotEnoughData
|
||
|
return
|
||
|
}
|
||
|
ps := make([]Position, int(binary.LittleEndian.Uint32(b)))
|
||
|
b = b[4:]
|
||
|
for j := 0; j < len(ps); j++ {
|
||
|
ps[j], b, err = fillPositionBytes(b, isCordZ)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
coordinates[i] = ps
|
||
|
}
|
||
|
bytesOut = b
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func level3CalculatedBBox(coordinates [][]Position, bbox *BBox, isPolygon bool) BBox {
|
||
|
if bbox != nil {
|
||
|
return *bbox
|
||
|
}
|
||
|
var bbox2 BBox
|
||
|
var i = 0
|
||
|
for _, ps := range coordinates {
|
||
|
i, bbox2 = positionBBox(i, bbox2, ps)
|
||
|
if isPolygon {
|
||
|
break // only the exterior ring should be calculated for a polygon
|
||
|
}
|
||
|
}
|
||
|
return bbox2
|
||
|
}
|
||
|
|
||
|
func level3Weight(coordinates [][]Position, bbox *BBox) int {
|
||
|
return level3PositionCount(coordinates, bbox) * sizeofPosition
|
||
|
}
|
||
|
|
||
|
func level3PositionCount(coordinates [][]Position, bbox *BBox) int {
|
||
|
var res int
|
||
|
for _, p := range coordinates {
|
||
|
res += len(p)
|
||
|
}
|
||
|
if bbox != nil {
|
||
|
return 2 + res
|
||
|
}
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func level3JSON(name string, coordinates [][]Position, bbox *BBox) string {
|
||
|
isCordZ := level3IsCoordZDefined(coordinates, bbox)
|
||
|
var buf bytes.Buffer
|
||
|
buf.WriteString(`{"type":"`)
|
||
|
buf.WriteString(name)
|
||
|
buf.WriteString(`","coordinates":[`)
|
||
|
for i, p := range coordinates {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(',')
|
||
|
}
|
||
|
buf.WriteByte('[')
|
||
|
for i, p := range p {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(',')
|
||
|
}
|
||
|
buf.WriteByte('[')
|
||
|
p.writeJSON(&buf, isCordZ)
|
||
|
buf.WriteByte(']')
|
||
|
}
|
||
|
buf.WriteByte(']')
|
||
|
}
|
||
|
buf.WriteByte(']')
|
||
|
bbox.write(&buf)
|
||
|
buf.WriteByte('}')
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func level3IsCoordZDefined(coordinates [][]Position, bbox *BBox) bool {
|
||
|
if bbox.isCordZDefined() {
|
||
|
return true
|
||
|
}
|
||
|
for _, p := range coordinates {
|
||
|
for _, p := range p {
|
||
|
if p.Z != nilz {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func level3Bytes(objType byte, coordinates [][]Position, bbox *BBox) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
isCordZ := level3IsCoordZDefined(coordinates, bbox)
|
||
|
writeHeader(&buf, objType, bbox, isCordZ)
|
||
|
b := make([]byte, 4)
|
||
|
binary.LittleEndian.PutUint32(b, uint32(len(coordinates)))
|
||
|
buf.Write(b)
|
||
|
for _, p := range coordinates {
|
||
|
binary.LittleEndian.PutUint32(b, uint32(len(p)))
|
||
|
buf.Write(b)
|
||
|
for _, p := range p {
|
||
|
p.writeBytes(&buf, isCordZ)
|
||
|
}
|
||
|
}
|
||
|
return buf.Bytes()
|
||
|
}
|
||
|
|
||
|
////////////////////////////////
|
||
|
// level 4
|
||
|
////////////////////////////////
|
||
|
|
||
|
func fillLevel4Map(m map[string]interface{}) (
|
||
|
coordinates [][][]Position, bbox *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
switch v := m["coordinates"].(type) {
|
||
|
default:
|
||
|
err = errInvalidCoordinates
|
||
|
return
|
||
|
case nil:
|
||
|
err = errCoordinatesRequired
|
||
|
return
|
||
|
case []interface{}:
|
||
|
coordinates = make([][][]Position, len(v))
|
||
|
for i, v := range v {
|
||
|
v, ok := v.([]interface{})
|
||
|
if !ok {
|
||
|
err = errInvalidCoordinatesValue
|
||
|
return
|
||
|
}
|
||
|
ps := make([][]Position, len(v))
|
||
|
for i, v := range v {
|
||
|
v, ok := v.([]interface{})
|
||
|
if !ok {
|
||
|
err = errInvalidCoordinatesValue
|
||
|
return
|
||
|
}
|
||
|
pss := make([]Position, len(v))
|
||
|
for i, v := range v {
|
||
|
v, ok := v.([]interface{})
|
||
|
if !ok {
|
||
|
err = errInvalidCoordinatesValue
|
||
|
return
|
||
|
}
|
||
|
var p Position
|
||
|
p, err = fillPosition(v)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
pss[i] = p
|
||
|
}
|
||
|
ps[i] = pss
|
||
|
}
|
||
|
coordinates[i] = ps
|
||
|
}
|
||
|
}
|
||
|
bbox, err = fillBBox(m)
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func fillLevel4Bytes(b []byte, bbox *BBox, isCordZ bool) (
|
||
|
coordinates [][][]Position, bboxOut *BBox, bytesOut []byte, err error,
|
||
|
) {
|
||
|
bboxOut = bbox
|
||
|
if len(b) < 4 {
|
||
|
err = errNotEnoughData
|
||
|
return
|
||
|
}
|
||
|
coordinates = make([][][]Position, int(binary.LittleEndian.Uint32(b)))
|
||
|
b = b[4:]
|
||
|
for i := 0; i < len(coordinates); i++ {
|
||
|
if len(b) < 4 {
|
||
|
err = errNotEnoughData
|
||
|
return
|
||
|
}
|
||
|
ps := make([][]Position, int(binary.LittleEndian.Uint32(b)))
|
||
|
b = b[4:]
|
||
|
for i := 0; i < len(ps); i++ {
|
||
|
if len(b) < 4 {
|
||
|
err = errNotEnoughData
|
||
|
return
|
||
|
}
|
||
|
pss := make([]Position, int(binary.LittleEndian.Uint32(b)))
|
||
|
b = b[4:]
|
||
|
for i := 0; i < len(pss); i++ {
|
||
|
pss[i], b, err = fillPositionBytes(b, isCordZ)
|
||
|
if err != nil {
|
||
|
return
|
||
|
}
|
||
|
}
|
||
|
ps[i] = pss
|
||
|
}
|
||
|
coordinates[i] = ps
|
||
|
}
|
||
|
bytesOut = b
|
||
|
return
|
||
|
}
|
||
|
|
||
|
func level4CalculatedBBox(coordinates [][][]Position, bbox *BBox) BBox {
|
||
|
if bbox != nil {
|
||
|
return *bbox
|
||
|
}
|
||
|
var bbox2 BBox
|
||
|
var i = 0
|
||
|
for _, ps := range coordinates {
|
||
|
for _, ps := range ps {
|
||
|
i, bbox2 = positionBBox(i, bbox2, ps)
|
||
|
}
|
||
|
}
|
||
|
return bbox2
|
||
|
}
|
||
|
|
||
|
func level4Weight(coordinates [][][]Position, bbox *BBox) int {
|
||
|
return level4PositionCount(coordinates, bbox) * sizeofPosition
|
||
|
}
|
||
|
|
||
|
func level4PositionCount(coordinates [][][]Position, bbox *BBox) int {
|
||
|
var res int
|
||
|
for _, p := range coordinates {
|
||
|
for _, p := range p {
|
||
|
res += len(p)
|
||
|
}
|
||
|
}
|
||
|
if bbox != nil {
|
||
|
return 2 + res
|
||
|
}
|
||
|
return res
|
||
|
}
|
||
|
|
||
|
func level4JSON(name string, coordinates [][][]Position, bbox *BBox) string {
|
||
|
isCordZ := level4IsCoordZDefined(coordinates, bbox)
|
||
|
var buf bytes.Buffer
|
||
|
buf.WriteString(`{"type":"`)
|
||
|
buf.WriteString(name)
|
||
|
buf.WriteString(`","coordinates":[`)
|
||
|
for i, p := range coordinates {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(',')
|
||
|
}
|
||
|
buf.WriteByte('[')
|
||
|
for i, p := range p {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(',')
|
||
|
}
|
||
|
buf.WriteByte('[')
|
||
|
for i, p := range p {
|
||
|
if i > 0 {
|
||
|
buf.WriteByte(',')
|
||
|
}
|
||
|
buf.WriteByte('[')
|
||
|
p.writeJSON(&buf, isCordZ)
|
||
|
buf.WriteByte(']')
|
||
|
}
|
||
|
buf.WriteByte(']')
|
||
|
}
|
||
|
buf.WriteByte(']')
|
||
|
}
|
||
|
buf.WriteByte(']')
|
||
|
bbox.write(&buf)
|
||
|
buf.WriteByte('}')
|
||
|
return buf.String()
|
||
|
}
|
||
|
|
||
|
func level4IsCoordZDefined(coordinates [][][]Position, bbox *BBox) bool {
|
||
|
if bbox.isCordZDefined() {
|
||
|
return true
|
||
|
}
|
||
|
for _, p := range coordinates {
|
||
|
for _, p := range p {
|
||
|
for _, p := range p {
|
||
|
if p.Z != nilz {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func level4Bytes(objType byte, coordinates [][][]Position, bbox *BBox) []byte {
|
||
|
var buf bytes.Buffer
|
||
|
isCordZ := level4IsCoordZDefined(coordinates, bbox)
|
||
|
writeHeader(&buf, objType, bbox, isCordZ)
|
||
|
b := make([]byte, 4)
|
||
|
binary.LittleEndian.PutUint32(b, uint32(len(coordinates)))
|
||
|
buf.Write(b)
|
||
|
for _, p := range coordinates {
|
||
|
binary.LittleEndian.PutUint32(b, uint32(len(p)))
|
||
|
buf.Write(b)
|
||
|
for _, p := range p {
|
||
|
binary.LittleEndian.PutUint32(b, uint32(len(p)))
|
||
|
buf.Write(b)
|
||
|
for _, p := range p {
|
||
|
p.writeBytes(&buf, isCordZ)
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return buf.Bytes()
|
||
|
}
|