Precalculate and store bboxes for complex objects

This should increase the performance for most search operations such as
WITHIN, INTERSECTS, and NEARBY when dealing with complex polygons.
Geofencing should see a increase in throughput when dealing with a high
frequency of point -> polygon detection.

Addresses #245
This commit is contained in:
Josh Baker 2018-01-11 13:57:18 -07:00
parent 44a6acf107
commit 1f70cb4e75
19 changed files with 971 additions and 323 deletions

View File

@ -137,17 +137,6 @@ func (c *Controller) aofshrink() {
values = append(values, "point")
values = append(values, strconv.FormatFloat(obj.Y, 'f', -1, 64))
values = append(values, strconv.FormatFloat(obj.X, 'f', -1, 64))
case geojson.Point:
if obj.Coordinates.Z == 0 {
values = append(values, "point")
values = append(values, strconv.FormatFloat(obj.Coordinates.Y, 'f', -1, 64))
values = append(values, strconv.FormatFloat(obj.Coordinates.X, 'f', -1, 64))
values = append(values, strconv.FormatFloat(obj.Coordinates.Z, 'f', -1, 64))
} else {
values = append(values, "point")
values = append(values, strconv.FormatFloat(obj.Coordinates.Y, 'f', -1, 64))
values = append(values, strconv.FormatFloat(obj.Coordinates.X, 'f', -1, 64))
}
}
// append the values to the aof buffer

View File

@ -1,7 +1,6 @@
package geojson
import (
"bytes"
"math"
"strconv"
@ -63,28 +62,29 @@ func (b *BBox) isCordZDefined() bool {
return b != nil && (b.Min.Z != nilz || b.Max.Z != nilz)
}
func (b *BBox) write(buf *bytes.Buffer) {
func appendBBoxJSON(json []byte, b *BBox) []byte {
if b == nil {
return
return json
}
hasZ := b.Min.Z != nilz && b.Max.Z != nilz
buf.WriteString(`,"bbox":[`)
buf.WriteString(strconv.FormatFloat(b.Min.X, 'f', -1, 64))
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Min.Y, 'f', -1, 64))
json = append(json, `,"bbox":[`...)
json = strconv.AppendFloat(json, b.Min.X, 'f', -1, 64)
json = append(json, ',')
json = strconv.AppendFloat(json, b.Min.Y, 'f', -1, 64)
if hasZ {
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Min.Z, 'f', -1, 64))
json = append(json, ',')
json = strconv.AppendFloat(json, b.Min.Z, 'f', -1, 64)
}
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Max.X, 'f', -1, 64))
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Max.Y, 'f', -1, 64))
json = append(json, ',')
json = strconv.AppendFloat(json, b.Max.X, 'f', -1, 64)
json = append(json, ',')
json = strconv.AppendFloat(json, b.Max.Y, 'f', -1, 64)
if hasZ {
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(b.Max.Z, 'f', -1, 64))
json = append(json, ',')
json = strconv.AppendFloat(json, b.Max.Z, 'f', -1, 64)
}
buf.WriteByte(']')
json = append(json, ']')
return json
}
func (b BBox) center() Position {

View File

@ -1,7 +1,6 @@
package geojson
import (
"bytes"
"encoding/binary"
"github.com/tidwall/gjson"
@ -10,9 +9,10 @@ import (
// Feature is a geojson object with the type "Feature"
type Feature struct {
Geometry Object
BBox *BBox
idprops string // raw id and properties seperated by a '\0'
Geometry Object
BBox *BBox
bboxDefined bool
idprops string // raw id and properties seperated by a '\0'
}
func fillFeatureMap(json string) (Feature, error) {
@ -35,7 +35,11 @@ func fillFeatureMap(json string) (Feature, error) {
if err != nil {
return g, err
}
g.bboxDefined = g.BBox != nil
if !g.bboxDefined {
cbbox := g.CalculatedBBox()
g.BBox = &cbbox
}
var propsExists bool
props := gjson.Get(json, "properties")
switch props.Type {
@ -89,7 +93,7 @@ func (g Feature) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g Feature) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g Feature) getRaw() (id, props string) {
@ -128,23 +132,27 @@ func makeCompositeRaw(idRaw, propsRaw string) string {
return string(raw)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g Feature) JSON() string {
var buf bytes.Buffer
buf.WriteString(`{"type":"Feature","geometry":`)
buf.WriteString(g.Geometry.JSON())
g.BBox.write(&buf)
func (g Feature) appendJSON(json []byte) []byte {
json = append(json, `{"type":"Feature","geometry":`...)
json = append(json, g.Geometry.JSON()...)
if g.bboxDefined {
json = appendBBoxJSON(json, g.BBox)
}
idRaw, propsRaw := g.getRaw()
if propsRaw != "" {
buf.WriteString(`,"properties":`)
buf.WriteString(propsRaw)
json = append(json, `,"properties":`...)
json = append(json, propsRaw...)
}
if idRaw != "" {
buf.WriteString(`,"id":`)
buf.WriteString(idRaw)
json = append(json, `,"id":`...)
json = append(json, idRaw...)
}
buf.WriteByte('}')
return buf.String()
return append(json, '}')
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g Feature) JSON() string {
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -160,7 +168,7 @@ func (g Feature) bboxPtr() *BBox {
return g.BBox
}
func (g Feature) hasPositions() bool {
if g.BBox != nil {
if g.bboxDefined {
return true
}
return g.Geometry.hasPositions()
@ -168,7 +176,7 @@ func (g Feature) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g Feature) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
return g.Geometry.WithinBBox(bbox)
@ -176,7 +184,7 @@ func (g Feature) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g Feature) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
return g.Geometry.IntersectsBBox(bbox)
@ -213,7 +221,7 @@ func (g Feature) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g Feature) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -1,6 +1,8 @@
package geojson
import "testing"
import (
"testing"
)
func TestFeature(t *testing.T) {
testJSON(t, `{
@ -116,3 +118,652 @@ func TestComplexFeature(t *testing.T) {
}
o = o
}
func TestIssue245(t *testing.T) {
json := `{
"type": "Feature",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[113.840391, 23.36073],
[113.840837, 23.360618],
[113.841198, 23.360528],
[113.84295, 23.360252],
[113.844386, 23.359914],
[113.844674, 23.359847],
[113.845843, 23.359466],
[113.846787, 23.358753],
[113.84679, 23.358751],
[113.847128, 23.358254],
[113.847571, 23.357602],
[113.847573, 23.357598],
[113.848175, 23.356521],
[113.848177, 23.356517],
[113.848187, 23.3565],
[113.848189, 23.356497],
[113.84847, 23.355449],
[113.848471, 23.355445],
[113.848475, 23.355126],
[113.848486, 23.354203],
[113.848373, 23.353516],
[113.848122, 23.352467],
[113.847432, 23.351258],
[113.846102, 23.349542],
[113.845853, 23.349288],
[113.84494, 23.348358],
[113.844435, 23.347908],
[113.843638, 23.347198],
[113.840895, 23.345392],
[113.836433, 23.342373],
[113.835963, 23.342033],
[113.83469, 23.34111],
[113.833801, 23.340465],
[113.831238, 23.338917],
[113.830698, 23.33835],
[113.830644, 23.337639],
[113.830642, 23.337639],
[113.825764, 23.337862],
[113.82575, 23.337897],
[113.82575, 23.337899],
[113.825749, 23.337901],
[113.825748, 23.337905],
[113.825747, 23.337907],
[113.825746, 23.337909],
[113.825746, 23.337912],
[113.825745, 23.337914],
[113.825744, 23.337916],
[113.825743, 23.337919],
[113.825742, 23.337921],
[113.825741, 23.337923],
[113.82574, 23.337926],
[113.825739, 23.337928],
[113.825738, 23.33793],
[113.825738, 23.337933],
[113.825737, 23.337935],
[113.825736, 23.337937],
[113.825735, 23.33794],
[113.825734, 23.337942],
[113.825733, 23.337944],
[113.825732, 23.337947],
[113.825731, 23.337949],
[113.82573, 23.337951],
[113.825729, 23.337954],
[113.825728, 23.337956],
[113.825727, 23.337959],
[113.825726, 23.337962],
[113.825725, 23.337964],
[113.825725, 23.337966],
[113.825724, 23.337969],
[113.825723, 23.337971],
[113.825722, 23.337973],
[113.825721, 23.337975],
[113.82572, 23.337978],
[113.825719, 23.33798],
[113.825718, 23.337982],
[113.825717, 23.337985],
[113.825717, 23.337987],
[113.825716, 23.337989],
[113.825715, 23.337992],
[113.825715, 23.337994],
[113.825714, 23.337996],
[113.825713, 23.337999],
[113.825712, 23.338001],
[113.825711, 23.338003],
[113.82571, 23.338006],
[113.825709, 23.338008],
[113.825708, 23.33801],
[113.825707, 23.338013],
[113.825706, 23.338015],
[113.825705, 23.338018],
[113.825705, 23.338021],
[113.825704, 23.338023],
[113.825703, 23.338025],
[113.825702, 23.338028],
[113.825701, 23.33803],
[113.8257, 23.338032],
[113.825699, 23.338035],
[113.825698, 23.338037],
[113.825697, 23.338039],
[113.825696, 23.338042],
[113.825696, 23.338044],
[113.825695, 23.338046],
[113.825694, 23.338049],
[113.825693, 23.338051],
[113.825692, 23.338053],
[113.825691, 23.338056],
[113.82569, 23.338058],
[113.82569, 23.338061],
[113.825689, 23.338063],
[113.825689, 23.338065],
[113.825688, 23.338068],
[113.825687, 23.33807],
[113.825686, 23.338072],
[113.825685, 23.338075],
[113.825684, 23.338078],
[113.825683, 23.33808],
[113.825682, 23.338083],
[113.825682, 23.338085],
[113.825681, 23.338087],
[113.82568, 23.33809],
[113.825679, 23.338092],
[113.825678, 23.338094],
[113.825677, 23.338097],
[113.825676, 23.338099],
[113.825675, 23.338101],
[113.825675, 23.338104],
[113.825674, 23.338106],
[113.825673, 23.338108],
[113.825672, 23.338111],
[113.825671, 23.338113],
[113.82567, 23.338115],
[113.825669, 23.338118],
[113.825669, 23.33812],
[113.825668, 23.338123],
[113.825668, 23.338125],
[113.825667, 23.338127],
[113.825666, 23.33813],
[113.825665, 23.338132],
[113.825664, 23.338134],
[113.825664, 23.338138],
[113.825663, 23.33814],
[113.825662, 23.338142],
[113.825661, 23.338145],
[113.82566, 23.338147],
[113.825659, 23.338149],
[113.825659, 23.338152],
[113.825658, 23.338154],
[113.825657, 23.338156],
[113.825656, 23.338159],
[113.825655, 23.338161],
[113.825654, 23.338164],
[113.825654, 23.338166],
[113.825653, 23.338168],
[113.825652, 23.338171],
[113.825651, 23.338173],
[113.82565, 23.338175],
[113.82565, 23.338178],
[113.825649, 23.33818],
[113.825648, 23.338182],
[113.825647, 23.338185],
[113.825646, 23.338187],
[113.825646, 23.33819],
[113.825646, 23.338192],
[113.825645, 23.338194],
[113.825644, 23.338197],
[113.825643, 23.3382],
[113.825642, 23.338202],
[113.825642, 23.338205],
[113.825641, 23.338207],
[113.82564, 23.33821],
[113.825639, 23.338212],
[113.825639, 23.338214],
[113.825638, 23.338217],
[113.825637, 23.338219],
[113.825636, 23.338221],
[113.825635, 23.338224],
[113.825635, 23.338226],
[113.825634, 23.338229],
[113.825633, 23.338231],
[113.825632, 23.338233],
[113.825632, 23.338236],
[113.825631, 23.338238],
[113.82563, 23.33824],
[113.825629, 23.338243],
[113.825628, 23.338245],
[113.825629, 23.338248],
[113.825628, 23.33825],
[113.825627, 23.338252],
[113.825626, 23.338255],
[113.825626, 23.338257],
[113.825625, 23.338259],
[113.825624, 23.338262],
[113.825623, 23.338265],
[113.825623, 23.338268],
[113.825622, 23.33827],
[113.825621, 23.338272],
[113.82562, 23.338275],
[113.82562, 23.338277],
[113.825619, 23.33828],
[113.825618, 23.338282],
[113.825618, 23.338284],
[113.825617, 23.338287],
[113.825616, 23.338289],
[113.825615, 23.338292],
[113.825532, 23.338833],
[113.825532, 23.338835],
[113.825532, 23.338838],
[113.825532, 23.33884],
[113.825532, 23.338843],
[113.825532, 23.338845],
[113.825531, 23.338848],
[113.825532, 23.33885],
[113.825532, 23.338853],
[113.825532, 23.338855],
[113.825532, 23.338858],
[113.825532, 23.33886],
[113.825532, 23.338863],
[113.825531, 23.338865],
[113.825531, 23.338868],
[113.825531, 23.33887],
[113.825531, 23.338873],
[113.825531, 23.338875],
[113.825531, 23.338878],
[113.825532, 23.33888],
[113.825532, 23.338883],
[113.825531, 23.338885],
[113.825531, 23.338888],
[113.825531, 23.33889],
[113.825531, 23.338893],
[113.825531, 23.338895],
[113.825531, 23.338898],
[113.825531, 23.3389],
[113.825531, 23.338903],
[113.825531, 23.338905],
[113.82553, 23.338908],
[113.825531, 23.33891],
[113.825531, 23.338913],
[113.825531, 23.338915],
[113.825531, 23.338918],
[113.825531, 23.33892],
[113.825531, 23.338923],
[113.825531, 23.338925],
[113.825531, 23.338928],
[113.825531, 23.33893],
[113.825531, 23.338933],
[113.82553, 23.338935],
[113.82553, 23.338938],
[113.825531, 23.33894],
[113.825531, 23.338942],
[113.825531, 23.338945],
[113.825531, 23.338947],
[113.825531, 23.33895],
[113.825531, 23.338952],
[113.825531, 23.338956],
[113.825531, 23.338958],
[113.825531, 23.338961],
[113.825531, 23.338963],
[113.825531, 23.338966],
[113.825532, 23.338968],
[113.825532, 23.338971],
[113.825532, 23.338973],
[113.825531, 23.338976],
[113.825531, 23.338978],
[113.825531, 23.338981],
[113.825531, 23.338983],
[113.825531, 23.338986],
[113.825531, 23.338988],
[113.825531, 23.338991],
[113.825531, 23.338993],
[113.825531, 23.338996],
[113.825532, 23.338998],
[113.825532, 23.339001],
[113.825532, 23.339003],
[113.825532, 23.339006],
[113.825532, 23.339008],
[113.825532, 23.339011],
[113.825532, 23.339013],
[113.825532, 23.339016],
[113.825532, 23.339018],
[113.825532, 23.339021],
[113.825532, 23.339023],
[113.825533, 23.339026],
[113.825533, 23.339028],
[113.825533, 23.339031],
[113.825533, 23.339033],
[113.825533, 23.339036],
[113.825533, 23.339038],
[113.825533, 23.339041],
[113.825533, 23.339043],
[113.825533, 23.339046],
[113.825533, 23.339048],
[113.825533, 23.339051],
[113.825533, 23.339053],
[113.825534, 23.339056],
[113.825534, 23.339058],
[113.825534, 23.339061],
[113.825534, 23.339063],
[113.825534, 23.339066],
[113.825534, 23.339068],
[113.825534, 23.339071],
[113.825534, 23.339073],
[113.825534, 23.339076],
[113.825534, 23.339078],
[113.825534, 23.339081],
[113.825535, 23.339083],
[113.825535, 23.339086],
[113.825535, 23.339088],
[113.825535, 23.339091],
[113.825535, 23.339093],
[113.825535, 23.339096],
[113.825535, 23.339098],
[113.825535, 23.339101],
[113.825535, 23.339103],
[113.825535, 23.339106],
[113.825535, 23.339108],
[113.825536, 23.339111],
[113.825536, 23.339113],
[113.825536, 23.339116],
[113.825536, 23.339118],
[113.825536, 23.339121],
[113.825536, 23.339123],
[113.825536, 23.339125],
[113.825536, 23.339128],
[113.825536, 23.33913],
[113.825536, 23.339133],
[113.825536, 23.339135],
[113.825537, 23.339138],
[113.825537, 23.33914],
[113.825538, 23.339143],
[113.825538, 23.339145],
[113.825538, 23.339148],
[113.825538, 23.33915],
[113.825538, 23.339153],
[113.825538, 23.339156],
[113.825538, 23.339159],
[113.825538, 23.339161],
[113.825538, 23.339164],
[113.825539, 23.339166],
[113.825539, 23.339169],
[113.825539, 23.339171],
[113.825539, 23.339174],
[113.825539, 23.339176],
[113.825539, 23.339179],
[113.825539, 23.339181],
[113.825539, 23.339184],
[113.825539, 23.339186],
[113.825539, 23.339189],
[113.825539, 23.339191],
[113.82554, 23.339194],
[113.82554, 23.339196],
[113.825541, 23.339199],
[113.825541, 23.339201],
[113.825541, 23.339204],
[113.825541, 23.339206],
[113.825541, 23.339209],
[113.825541, 23.339211],
[113.825541, 23.339214],
[113.825541, 23.339216],
[113.825542, 23.339219],
[113.825542, 23.339221],
[113.825542, 23.339224],
[113.825542, 23.339226],
[113.825542, 23.339229],
[113.825542, 23.339231],
[113.825542, 23.339234],
[113.825542, 23.339236],
[113.825542, 23.339239],
[113.825542, 23.339241],
[113.825543, 23.339244],
[113.825544, 23.339246],
[113.825544, 23.339249],
[113.825544, 23.339251],
[113.825544, 23.339254],
[113.825544, 23.339256],
[113.825544, 23.339259],
[113.825544, 23.339261],
[113.825544, 23.339264],
[113.825544, 23.339266],
[113.825544, 23.339269],
[113.825544, 23.339271],
[113.825545, 23.339274],
[113.825545, 23.339276],
[113.825545, 23.339279],
[113.825545, 23.339281],
[113.825545, 23.339284],
[113.825675, 23.341405],
[113.825676, 23.341408],
[113.825676, 23.34141],
[113.825676, 23.341413],
[113.825676, 23.341415],
[113.825676, 23.341418],
[113.825676, 23.34142],
[113.825676, 23.341423],
[113.825676, 23.341425],
[113.825676, 23.341428],
[113.825676, 23.34143],
[113.825676, 23.341433],
[113.825676, 23.341435],
[113.825677, 23.341438],
[113.825677, 23.34144],
[113.825677, 23.341443],
[113.825678, 23.341445],
[113.825678, 23.341448],
[113.825678, 23.34145],
[113.825678, 23.341453],
[113.825678, 23.341455],
[113.825678, 23.341458],
[113.825678, 23.34146],
[113.825679, 23.341463],
[113.825679, 23.341465],
[113.825679, 23.341468],
[113.825679, 23.34147],
[113.825679, 23.341473],
[113.825679, 23.341475],
[113.825679, 23.341478],
[113.825679, 23.34148],
[113.825679, 23.341483],
[113.825679, 23.341485],
[113.82568, 23.341488],
[113.825681, 23.34149],
[113.825681, 23.341493],
[113.825681, 23.341495],
[113.825681, 23.341498],
[113.825681, 23.3415],
[113.825681, 23.341503],
[113.825681, 23.341505],
[113.825681, 23.341508],
[113.825681, 23.34151],
[113.825681, 23.341513],
[113.825681, 23.341515],
[113.825682, 23.341518],
[113.825682, 23.34152],
[113.825682, 23.341524],
[113.825682, 23.341526],
[113.825682, 23.341529],
[113.825682, 23.341531],
[113.825682, 23.341534],
[113.825683, 23.341536],
[113.825683, 23.341539],
[113.825683, 23.341541],
[113.825684, 23.341544],
[113.825684, 23.341546],
[113.825684, 23.341549],
[113.825684, 23.341551],
[113.825684, 23.341554],
[113.825684, 23.341556],
[113.825684, 23.341558],
[113.825684, 23.341561],
[113.825684, 23.341563],
[113.825684, 23.341566],
[113.825684, 23.341568],
[113.825684, 23.341571],
[113.825685, 23.341573],
[113.825685, 23.341576],
[113.825685, 23.341578],
[113.825685, 23.341581],
[113.825685, 23.341583],
[113.825686, 23.341586],
[113.825686, 23.341588],
[113.825686, 23.341591],
[113.825686, 23.341593],
[113.825686, 23.341596],
[113.825687, 23.341598],
[113.825687, 23.341601],
[113.825687, 23.341603],
[113.825687, 23.341606],
[113.825687, 23.341608],
[113.825687, 23.341611],
[113.825687, 23.341613],
[113.825687, 23.341616],
[113.825687, 23.341618],
[113.825687, 23.341621],
[113.825687, 23.341623],
[113.825688, 23.341626],
[113.825688, 23.341628],
[113.825688, 23.341631],
[113.825688, 23.341633],
[113.825688, 23.341636],
[113.825688, 23.341638],
[113.825688, 23.341641],
[113.825688, 23.341643],
[113.825688, 23.341646],
[113.825688, 23.341648],
[113.825689, 23.341651],
[113.82569, 23.341653],
[113.82569, 23.341656],
[113.82569, 23.341658],
[113.82569, 23.341661],
[113.82569, 23.341663],
[113.82569, 23.341666],
[113.82569, 23.341668],
[113.82569, 23.341671],
[113.82569, 23.341673],
[113.82569, 23.341676],
[113.82569, 23.341678],
[113.825691, 23.341681],
[113.825691, 23.341683],
[113.825691, 23.341686],
[113.825691, 23.341688],
[113.825691, 23.341691],
[113.825691, 23.341693],
[113.825691, 23.341696],
[113.825691, 23.341698],
[113.825691, 23.341701],
[113.825691, 23.341703],
[113.825691, 23.341706],
[113.825691, 23.341708],
[113.825692, 23.341711],
[113.825692, 23.341713],
[113.825692, 23.341716],
[113.825692, 23.341718],
[113.825692, 23.341721],
[113.825692, 23.341723],
[113.825692, 23.341726],
[113.825692, 23.341728],
[113.825692, 23.341731],
[113.825692, 23.341733],
[113.825693, 23.341736],
[113.825693, 23.341738],
[113.825693, 23.34174],
[113.825693, 23.341744],
[113.825693, 23.341746],
[113.825693, 23.341749],
[113.825693, 23.341751],
[113.825693, 23.341754],
[113.825693, 23.341756],
[113.825693, 23.341759],
[113.825693, 23.341761],
[113.825693, 23.341764],
[113.825694, 23.341766],
[113.825694, 23.341769],
[113.825694, 23.341771],
[113.825694, 23.341774],
[113.825694, 23.341776],
[113.825694, 23.341779],
[113.825694, 23.341781],
[113.825694, 23.341784],
[113.825694, 23.341786],
[113.825694, 23.341789],
[113.825694, 23.341791],
[113.825695, 23.341794],
[113.825695, 23.341796],
[113.825695, 23.341799],
[113.825695, 23.341801],
[113.825695, 23.341804],
[113.825695, 23.341806],
[113.825695, 23.341809],
[113.825695, 23.341811],
[113.825695, 23.341814],
[113.825695, 23.341816],
[113.825695, 23.341819],
[113.825696, 23.341821],
[113.825696, 23.341824],
[113.825696, 23.341826],
[113.825696, 23.341829],
[113.825696, 23.341831],
[113.825696, 23.341834],
[113.825696, 23.341836],
[113.825696, 23.341839],
[113.825696, 23.341841],
[113.825696, 23.341844],
[113.825696, 23.341846],
[113.825697, 23.341849],
[113.825697, 23.341851],
[113.825697, 23.341854],
[113.825696, 23.341856],
[113.825639, 23.342752],
[113.825909, 23.343038],
[113.826124, 23.34317],
[113.826246, 23.343207],
[113.826544, 23.343462],
[113.826663, 23.343725],
[113.826921, 23.344028],
[113.826946, 23.344045],
[113.827411, 23.344775],
[113.827452, 23.345071],
[113.827433, 23.345444],
[113.827373, 23.345688],
[113.827847, 23.346486],
[113.827867, 23.346492],
[113.828217, 23.346555],
[113.828812, 23.346578],
[113.829478, 23.346804],
[113.829926, 23.347127],
[113.830393, 23.34749],
[113.830756, 23.347827],
[113.83112, 23.348231],
[113.832415, 23.349689],
[113.833006, 23.350048],
[113.834533, 23.350448],
[113.835235, 23.350919],
[113.835526, 23.35132],
[113.836805, 23.353126],
[113.836931, 23.353474],
[113.837019, 23.35471],
[113.837041, 23.354934],
[113.837129, 23.355633],
[113.837135, 23.355731],
[113.837148, 23.35729],
[113.837124, 23.357391],
[113.837093, 23.357543],
[113.837116, 23.357842],
[113.837231, 23.358547],
[113.837488, 23.359569],
[113.837398, 23.359954],
[113.838019, 23.360873],
[113.838056, 23.360928],
[113.840391, 23.36073]
]
]
},
"properties": {
"center": {
"lat": 0,
"lon": 0
},
"smuserid": 960,
"area": 2807456.367185,
"refname": "10301",
"name": "",
"refid": 293,
"districtid": 440183,
"ver": "v1.20170705",
"class": "–°«¯",
"id": 960,
"verdate": "20170705",
"oriid": 1724
}
}
`
poly := testJSON(t, json).(Feature)
g, _ := ObjectJSON(`{"type":"Point","coordinates":[113.837019, 23.35471]}`)
if !g.Within(poly) {
t.Fatal("!")
}
g, _ = ObjectJSON(`{"type":"Point","coordinates":[114.837019, 23.35471]}`)
if g.Within(poly) {
t.Fatal("!")
}
}

View File

@ -1,16 +1,15 @@
package geojson
import (
"bytes"
"github.com/tidwall/gjson"
"github.com/tidwall/tile38/geojson/geohash"
)
// FeatureCollection is a geojson object with the type "FeatureCollection"
type FeatureCollection struct {
Features []Object
BBox *BBox
Features []Object
BBox *BBox
bboxDefined bool
}
func fillFeatureCollectionMap(json string) (FeatureCollection, error) {
@ -40,6 +39,14 @@ func fillFeatureCollectionMap(json string) (FeatureCollection, error) {
}
var err error
g.BBox, err = fillBBox(json)
if err != nil {
return g, err
}
g.bboxDefined = g.BBox != nil
if !g.bboxDefined {
cbbox := g.CalculatedBBox()
g.BBox = &cbbox
}
return g, err
}
@ -93,23 +100,27 @@ func (g FeatureCollection) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g FeatureCollection) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g FeatureCollection) appendJSON(json []byte) []byte {
json = append(json, `{"type":"FeatureCollection","features":[`...)
for i, g := range g.Features {
if i != 0 {
json = append(json, ',')
}
json = append(json, g.JSON()...)
}
json = append(json, ']')
if g.bboxDefined {
json = appendBBoxJSON(json, g.BBox)
}
return append(json, '}')
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g FeatureCollection) JSON() string {
var buf bytes.Buffer
buf.WriteString(`{"type":"FeatureCollection","features":[`)
for i, g := range g.Features {
if i != 0 {
buf.WriteByte(',')
}
buf.WriteString(g.JSON())
}
buf.WriteByte(']')
g.BBox.write(&buf)
buf.WriteByte('}')
return buf.String()
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -138,7 +149,7 @@ func (g FeatureCollection) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g FeatureCollection) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
if len(g.Features) == 0 {
@ -154,7 +165,7 @@ func (g FeatureCollection) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g FeatureCollection) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
for _, g := range g.Features {
@ -228,7 +239,7 @@ func (g FeatureCollection) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g FeatureCollection) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -40,6 +40,7 @@ func doesJSONMatch(js1, js2 string) bool {
}
func testJSON(t *testing.T, jstr string) Object {
t.Helper()
o, err := ObjectJSON(jstr)
if err != nil {
t.Fatal(err)
@ -47,11 +48,11 @@ func testJSON(t *testing.T, jstr string) Object {
if !doesJSONMatch(o.JSON(), jstr) {
t.Fatalf("expected '%v', got '%v'", o.JSON(), jstr)
}
return o
}
func testInvalidJSON(t *testing.T, js string, expecting error) {
t.Helper()
_, err := ObjectJSON(js)
if err == nil {
t.Fatalf("expecting an error for json '%s'", js)
@ -86,33 +87,3 @@ func TestInvalidJSON(t *testing.T) {
testInvalidJSON(t, `{"type":"Point","coordinates":[1,2,3],"bbox":[1,2,3]}`, errBBoxInvalidNumberOfValues)
testInvalidJSON(t, `{"type":"Point","coordinates":[1,2,3],"bbox":[1,2,3,"a"]}`, errBBoxInvalidValue)
}
// func TestJunk(t *testing.T) {
// type ThreePoint struct{ X, Y, Z float64 }
// var s1 = ThreePoint{50, 50, 50}
// var s2 = &s1
// var o1 interface{} = s1
// var o2 interface{} = s2
// t1 := reflect.TypeOf(s1)
// t2 := reflect.TypeOf(s2)
// t3 := reflect.TypeOf(o1)
// t4 := reflect.TypeOf(o2)
// fmt.Printf("typeof: %s\n", t1)
// fmt.Printf("typeof: %s\n", t2)
// fmt.Printf("typeof: %s\n", t3)
// fmt.Printf("typeof: %s\n", t4)
// z1 := unsafe.Sizeof(s1)
// z2 := unsafe.Sizeof(s2)
// z3 := unsafe.Sizeof(o1)
// z4 := unsafe.Sizeof(o2.(*ThreePoint))
// fmt.Printf("sizeof: %d\n", z1)
// fmt.Printf("sizeof: %d\n", z2)
// fmt.Printf("sizeof: %d\n", z3)
// fmt.Printf("sizeof: %d\n", z4)
// }

View File

@ -1,16 +1,15 @@
package geojson
import (
"bytes"
"github.com/tidwall/gjson"
"github.com/tidwall/tile38/geojson/geohash"
)
// GeometryCollection is a geojson object with the type "GeometryCollection"
type GeometryCollection struct {
Geometries []Object
BBox *BBox
Geometries []Object
BBox *BBox
bboxDefined bool
}
func fillGeometryCollectionMap(json string) (GeometryCollection, error) {
@ -40,6 +39,14 @@ func fillGeometryCollectionMap(json string) (GeometryCollection, error) {
}
var err error
g.BBox, err = fillBBox(json)
if err != nil {
return g, err
}
g.bboxDefined = g.BBox != nil
if !g.bboxDefined {
cbbox := g.CalculatedBBox()
g.BBox = &cbbox
}
return g, err
}
@ -93,23 +100,26 @@ func (g GeometryCollection) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g GeometryCollection) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g GeometryCollection) appendJSON(json []byte) []byte {
json = append(json, `{"type":"GeometryCollection","geometries":[`...)
for i, g := range g.Geometries {
if i != 0 {
json = append(json, ',')
}
json = append(json, g.JSON()...)
}
json = append(json, ']')
if g.bboxDefined {
json = appendBBoxJSON(json, g.BBox)
}
return append(json, '}')
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g GeometryCollection) JSON() string {
var buf bytes.Buffer
buf.WriteString(`{"type":"GeometryCollection","geometries":[`)
for i, g := range g.Geometries {
if i != 0 {
buf.WriteByte(',')
}
buf.WriteString(g.JSON())
}
buf.WriteByte(']')
g.BBox.write(&buf)
buf.WriteByte('}')
return buf.String()
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -138,7 +148,7 @@ func (g GeometryCollection) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g GeometryCollection) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
if len(g.Geometries) == 0 {
@ -154,7 +164,7 @@ func (g GeometryCollection) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g GeometryCollection) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
for _, g := range g.Geometries {
@ -228,7 +238,7 @@ func (g GeometryCollection) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g GeometryCollection) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -1,10 +1,6 @@
package geojson
import (
"bytes"
"github.com/tidwall/gjson"
)
import "github.com/tidwall/gjson"
func resIsArray(res gjson.Result) bool {
if res.Type == gjson.JSON {
@ -71,17 +67,20 @@ func level1Weight(coordinates Position, bbox *BBox) int {
return level1PositionCount(coordinates, bbox) * sizeofPosition
}
func level1JSON(name string, coordinates Position, bbox *BBox) string {
func appendLevel1JSON(json []byte, name string, coordinates Position, bbox *BBox, bboxDefined bool) []byte {
if bbox != nil && !bboxDefined {
bbox = nil
}
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()
json = append(json, `{"type":"`...)
json = append(json, name...)
json = append(json, `","coordinates":[`...)
json = appendPositionJSON(json, coordinates, isCordZ)
json = append(json, ']')
if bboxDefined {
json = appendBBoxJSON(json, bbox)
}
return append(json, '}')
}
func level1IsCoordZDefined(coordinates Position, bbox *BBox) bool {
@ -149,24 +148,28 @@ func level2Weight(coordinates []Position, bbox *BBox) int {
return level2PositionCount(coordinates, bbox) * sizeofPosition
}
func level2JSON(name string, coordinates []Position, bbox *BBox) string {
func appendLevel2JSON(json []byte, name string, coordinates []Position, bbox *BBox, bboxDefined bool) []byte {
if bbox != nil && !bboxDefined {
bbox = nil
}
isCordZ := level2IsCoordZDefined(coordinates, bbox)
var buf bytes.Buffer
buf.WriteString(`{"type":"`)
buf.WriteString(name)
buf.WriteString(`","coordinates":[`)
json = append(json, `{"type":"`...)
json = append(json, name...)
json = append(json, `","coordinates":[`...)
for i, p := range coordinates {
if i > 0 {
buf.WriteByte(',')
json = append(json, ',')
}
buf.WriteByte('[')
p.writeJSON(&buf, isCordZ)
buf.WriteByte(']')
json = append(json, '[')
json = appendPositionJSON(json, p, isCordZ)
json = append(json, ']')
}
buf.WriteByte(']')
bbox.write(&buf)
buf.WriteByte('}')
return buf.String()
json = append(json, ']')
if bboxDefined {
json = appendBBoxJSON(json, bbox)
}
json = append(json, '}')
return json
}
func level2IsCoordZDefined(coordinates []Position, bbox *BBox) bool {
@ -259,31 +262,34 @@ func level3PositionCount(coordinates [][]Position, bbox *BBox) int {
return res
}
func level3JSON(name string, coordinates [][]Position, bbox *BBox) string {
func appendLevel3JSON(json []byte, name string, coordinates [][]Position, bbox *BBox, bboxDefined bool) []byte {
if bbox != nil && !bboxDefined {
bbox = nil
}
isCordZ := level3IsCoordZDefined(coordinates, bbox)
var buf bytes.Buffer
buf.WriteString(`{"type":"`)
buf.WriteString(name)
buf.WriteString(`","coordinates":[`)
json = append(json, `{"type":"`...)
json = append(json, name...)
json = append(json, `","coordinates":[`...)
for i, p := range coordinates {
if i > 0 {
buf.WriteByte(',')
json = append(json, ',')
}
buf.WriteByte('[')
json = append(json, '[')
for i, p := range p {
if i > 0 {
buf.WriteByte(',')
json = append(json, ',')
}
buf.WriteByte('[')
p.writeJSON(&buf, isCordZ)
buf.WriteByte(']')
json = append(json, '[')
json = appendPositionJSON(json, p, isCordZ)
json = append(json, ']')
}
buf.WriteByte(']')
json = append(json, ']')
}
buf.WriteByte(']')
bbox.write(&buf)
buf.WriteByte('}')
return buf.String()
json = append(json, ']')
if bboxDefined {
json = appendBBoxJSON(json, bbox)
}
return append(json, '}')
}
func level3IsCoordZDefined(coordinates [][]Position, bbox *BBox) bool {
@ -388,38 +394,41 @@ func level4PositionCount(coordinates [][][]Position, bbox *BBox) int {
return res
}
func level4JSON(name string, coordinates [][][]Position, bbox *BBox) string {
func appendLevel4JSON(json []byte, name string, coordinates [][][]Position, bbox *BBox, bboxDefined bool) []byte {
if bbox != nil && !bboxDefined {
bbox = nil
}
isCordZ := level4IsCoordZDefined(coordinates, bbox)
var buf bytes.Buffer
buf.WriteString(`{"type":"`)
buf.WriteString(name)
buf.WriteString(`","coordinates":[`)
json = append(json, `{"type":"`...)
json = append(json, name...)
json = append(json, `","coordinates":[`...)
for i, p := range coordinates {
if i > 0 {
buf.WriteByte(',')
json = append(json, ',')
}
buf.WriteByte('[')
json = append(json, '[')
for i, p := range p {
if i > 0 {
buf.WriteByte(',')
json = append(json, ',')
}
buf.WriteByte('[')
json = append(json, '[')
for i, p := range p {
if i > 0 {
buf.WriteByte(',')
json = append(json, ',')
}
buf.WriteByte('[')
p.writeJSON(&buf, isCordZ)
buf.WriteByte(']')
json = append(json, '[')
json = appendPositionJSON(json, p, isCordZ)
json = append(json, ']')
}
buf.WriteByte(']')
json = append(json, ']')
}
buf.WriteByte(']')
json = append(json, ']')
}
buf.WriteByte(']')
bbox.write(&buf)
buf.WriteByte('}')
return buf.String()
json = append(json, ']')
if bboxDefined {
json = appendBBoxJSON(json, bbox)
}
return append(json, '}')
}
func level4IsCoordZDefined(coordinates [][][]Position, bbox *BBox) bool {

View File

@ -6,6 +6,7 @@ import "github.com/tidwall/tile38/geojson/geohash"
type LineString struct {
Coordinates []Position
BBox *BBox
bboxDefined bool
}
func fillLineString(coordinates []Position, bbox *BBox, err error) (LineString, error) {
@ -14,9 +15,15 @@ func fillLineString(coordinates []Position, bbox *BBox, err error) (LineString,
err = errLineStringInvalidCoordinates
}
}
bboxDefined := bbox != nil
if !bboxDefined {
cbbox := level2CalculatedBBox(coordinates, nil)
bbox = &cbbox
}
return LineString{
Coordinates: coordinates,
BBox: bbox,
bboxDefined: bboxDefined,
}, err
}
@ -46,14 +53,18 @@ func (g LineString) Weight() int {
return level2Weight(g.Coordinates, g.BBox)
}
func (g LineString) appendJSON(json []byte) []byte {
return appendLevel2JSON(json, "LineString", g.Coordinates, g.BBox, g.bboxDefined)
}
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g LineString) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g LineString) JSON() string {
return level2JSON("LineString", g.Coordinates, g.BBox)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -64,13 +75,14 @@ func (g LineString) String() string {
func (g LineString) bboxPtr() *BBox {
return g.BBox
}
func (g LineString) hasPositions() bool {
return g.BBox != nil || len(g.Coordinates) > 0
return g.bboxDefined || len(g.Coordinates) > 0
}
// WithinBBox detects if the object is fully contained inside a bbox.
func (g LineString) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
return polyPositions(g.Coordinates).InsideRect(rectBBox(bbox))
@ -78,7 +90,7 @@ func (g LineString) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g LineString) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
return polyPositions(g.Coordinates).IntersectsRect(rectBBox(bbox))
@ -125,7 +137,7 @@ func (g LineString) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g LineString) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -9,6 +9,7 @@ import (
type MultiLineString struct {
Coordinates [][]Position
BBox *BBox
bboxDefined bool
}
func fillMultiLineString(coordinates [][]Position, bbox *BBox, err error) (MultiLineString, error) {
@ -20,9 +21,15 @@ func fillMultiLineString(coordinates [][]Position, bbox *BBox, err error) (Multi
}
}
}
bboxDefined := bbox != nil
if !bboxDefined {
cbbox := level3CalculatedBBox(coordinates, nil, false)
bbox = &cbbox
}
return MultiLineString{
Coordinates: coordinates,
BBox: bbox,
bboxDefined: bboxDefined,
}, err
}
@ -54,12 +61,16 @@ func (g MultiLineString) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g MultiLineString) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g MultiLineString) appendJSON(json []byte) []byte {
return appendLevel3JSON(json, "MultiLineString", g.Coordinates, g.BBox, g.bboxDefined)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g MultiLineString) JSON() string {
return level3JSON("MultiLineString", g.Coordinates, g.BBox)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -71,7 +82,7 @@ func (g MultiLineString) bboxPtr() *BBox {
return g.BBox
}
func (g MultiLineString) hasPositions() bool {
if g.BBox != nil {
if g.bboxDefined {
return true
}
for _, c := range g.Coordinates {
@ -84,7 +95,7 @@ func (g MultiLineString) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g MultiLineString) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
if len(g.Coordinates) == 0 {
@ -105,7 +116,7 @@ func (g MultiLineString) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g MultiLineString) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
for _, ls := range g.Coordinates {
@ -183,7 +194,7 @@ func (g MultiLineString) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g MultiLineString) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -1,6 +1,8 @@
package geojson
import "testing"
import (
"testing"
)
func TestMultiLineString(t *testing.T) {
testJSON(t, `{"type":"MultiLineString","coordinates":[[[100.1,5.1],[101.1,6.1]],[[102.1,7.1],[103.1,8.1]]]}`)

View File

@ -9,12 +9,19 @@ import (
type MultiPoint struct {
Coordinates []Position
BBox *BBox
bboxDefined bool
}
func fillMultiPoint(coordinates []Position, bbox *BBox, err error) (MultiPoint, error) {
bboxDefined := bbox != nil
if !bboxDefined {
cbbox := level2CalculatedBBox(coordinates, nil)
bbox = &cbbox
}
return MultiPoint{
Coordinates: coordinates,
BBox: bbox,
bboxDefined: bboxDefined,
}, err
}
@ -46,12 +53,16 @@ func (g MultiPoint) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g MultiPoint) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g MultiPoint) appendJSON(json []byte) []byte {
return appendLevel2JSON(json, "MultiPoint", g.Coordinates, g.BBox, g.bboxDefined)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g MultiPoint) JSON() string {
return level2JSON("MultiPoint", g.Coordinates, g.BBox)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -63,12 +74,12 @@ func (g MultiPoint) bboxPtr() *BBox {
return g.BBox
}
func (g MultiPoint) hasPositions() bool {
return g.BBox != nil || len(g.Coordinates) > 0
return g.bboxDefined || len(g.Coordinates) > 0
}
// WithinBBox detects if the object is fully contained inside a bbox.
func (g MultiPoint) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
if len(g.Coordinates) == 0 {
@ -162,7 +173,7 @@ func (g MultiPoint) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g MultiPoint) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -6,6 +6,7 @@ import "github.com/tidwall/tile38/geojson/geohash"
type MultiPolygon struct {
Coordinates [][][]Position
BBox *BBox
bboxDefined bool
}
func fillMultiPolygon(coordinates [][][]Position, bbox *BBox, err error) (MultiPolygon, error) {
@ -24,9 +25,15 @@ func fillMultiPolygon(coordinates [][][]Position, bbox *BBox, err error) (MultiP
}
}
}
bboxDefined := bbox != nil
if !bboxDefined {
cbbox := level4CalculatedBBox(coordinates, nil)
bbox = &cbbox
}
return MultiPolygon{
Coordinates: coordinates,
BBox: bbox,
bboxDefined: bboxDefined,
}, err
}
@ -58,12 +65,16 @@ func (g MultiPolygon) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g MultiPolygon) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g MultiPolygon) appendJSON(json []byte) []byte {
return appendLevel4JSON(json, "MultiPolygon", g.Coordinates, g.BBox, g.bboxDefined)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g MultiPolygon) JSON() string {
return level4JSON("MultiPolygon", g.Coordinates, g.BBox)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -75,7 +86,7 @@ func (g MultiPolygon) bboxPtr() *BBox {
return g.BBox
}
func (g MultiPolygon) hasPositions() bool {
if g.BBox != nil {
if g.bboxDefined {
return true
}
for _, c := range g.Coordinates {
@ -90,7 +101,7 @@ func (g MultiPolygon) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g MultiPolygon) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
if len(g.Coordinates) == 0 {
@ -192,7 +203,7 @@ func (g MultiPolygon) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g MultiPolygon) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -1,12 +1,9 @@
package geojson
import (
"bytes"
"encoding/binary"
"encoding/json"
"errors"
"fmt"
"math"
"github.com/tidwall/gjson"
"github.com/tidwall/tile38/geojson/poly"
@ -61,6 +58,8 @@ const nilz = 0
type Object interface {
bboxPtr() *BBox
hasPositions() bool
appendJSON(dst []byte) []byte
// WithinBBox detects if the object is fully contained inside a bbox.
WithinBBox(bbox BBox) bool
// IntersectsBBox detects if the object intersects a bbox.
@ -93,39 +92,6 @@ type Object interface {
IsGeometry() bool
}
func writeHeader(buf *bytes.Buffer, objType byte, bbox *BBox, isCordZ bool) {
header := objType
if bbox != nil {
header |= 1 << 4
if bbox.Min.Z != nilz || bbox.Max.Z != nilz {
header |= 1 << 5
}
}
if isCordZ {
header |= 1 << 6
}
buf.WriteByte(header)
if bbox != nil {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, math.Float64bits(bbox.Min.X))
buf.Write(b)
binary.LittleEndian.PutUint64(b, math.Float64bits(bbox.Min.Y))
buf.Write(b)
if bbox.Min.Z != nilz || bbox.Max.Z != nilz {
binary.LittleEndian.PutUint64(b, math.Float64bits(bbox.Min.Z))
buf.Write(b)
}
binary.LittleEndian.PutUint64(b, math.Float64bits(bbox.Max.X))
buf.Write(b)
binary.LittleEndian.PutUint64(b, math.Float64bits(bbox.Max.Y))
buf.Write(b)
if bbox.Min.Z != nilz || bbox.Max.Z != nilz {
binary.LittleEndian.PutUint64(b, math.Float64bits(bbox.Max.Z))
buf.Write(b)
}
}
}
func positionBBox(i int, bbox BBox, ps []Position) (int, BBox) {
for _, p := range ps {
if i == 0 {
@ -219,7 +185,12 @@ func objectMap(json string, from int) (Object, error) {
func withinObjectShared(g Object, o Object, pin func(v Polygon) bool, mpin func(v MultiPolygon) bool) bool {
bbp := o.bboxPtr()
if bbp != nil {
return g.WithinBBox(*bbp)
if !g.WithinBBox(*bbp) {
return false
}
if o.IsBBoxDefined() {
return true
}
}
switch v := o.(type) {
default:
@ -266,7 +237,12 @@ func withinObjectShared(g Object, o Object, pin func(v Polygon) bool, mpin func(
func intersectsObjectShared(g Object, o Object, pin func(v Polygon) bool, mpin func(v MultiPolygon) bool) bool {
bbp := o.bboxPtr()
if bbp != nil {
return g.IntersectsBBox(*bbp)
if !g.IntersectsBBox(*bbp) {
return false
}
if o.IsBBoxDefined() {
return true
}
}
switch v := o.(type) {
default:

View File

@ -10,6 +10,7 @@ import (
type Point struct {
Coordinates Position
BBox *BBox
bboxDefined bool
}
func fillSimplePointOrPoint(coordinates Position, bbox *BBox, err error) (Object, error) {
@ -20,9 +21,15 @@ func fillSimplePointOrPoint(coordinates Position, bbox *BBox, err error) (Object
}
func fillPoint(coordinates Position, bbox *BBox, err error) (Point, error) {
bboxDefined := bbox != nil
if !bboxDefined {
cbbox := level1CalculatedBBox(coordinates, nil)
bbox = &cbbox
}
return Point{
Coordinates: coordinates,
BBox: bbox,
bboxDefined: bboxDefined,
}, err
}
@ -47,12 +54,16 @@ func (g Point) Geohash(precision int) (string, error) {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g Point) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g Point) appendJSON(json []byte) []byte {
return appendLevel1JSON(json, "Point", g.Coordinates, g.BBox, g.bboxDefined)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g Point) JSON() string {
return level1JSON("Point", g.Coordinates, g.BBox)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -78,7 +89,7 @@ func (g Point) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g Point) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
return poly.Point(g.Coordinates).InsideRect(rectBBox(bbox))
@ -86,7 +97,7 @@ func (g Point) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g Point) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
return poly.Point(g.Coordinates).InsideRect(rectBBox(bbox))
@ -133,7 +144,7 @@ func (g Point) Nearby(center Position, meters float64) bool {
// IsBBoxDefined returns true if the object has a defined bbox.
func (g Point) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -1,16 +1,12 @@
package geojson
import (
"bytes"
"strconv"
"github.com/tidwall/tile38/geojson/geohash"
)
import "github.com/tidwall/tile38/geojson/geohash"
// Polygon is a geojson object with the type "Polygon"
type Polygon struct {
Coordinates [][]Position
BBox *BBox
bboxDefined bool
}
func fillPolygon(coordinates [][]Position, bbox *BBox, err error) (Polygon, error) {
@ -27,9 +23,15 @@ func fillPolygon(coordinates [][]Position, bbox *BBox, err error) (Polygon, erro
}
}
}
bboxDefined := bbox != nil
if !bboxDefined {
cbbox := level3CalculatedBBox(coordinates, nil, true)
bbox = &cbbox
}
return Polygon{
Coordinates: coordinates,
BBox: bbox,
bboxDefined: bboxDefined,
}, err
}
@ -61,12 +63,16 @@ func (g Polygon) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g Polygon) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g Polygon) appendJSON(json []byte) []byte {
return appendLevel3JSON(json, "Polygon", g.Coordinates, g.BBox, g.bboxDefined)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g Polygon) JSON() string {
return level3JSON("Polygon", g.Coordinates, g.BBox)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -78,7 +84,7 @@ func (g Polygon) bboxPtr() *BBox {
return g.BBox
}
func (g Polygon) hasPositions() bool {
if g.BBox != nil {
if g.bboxDefined {
return true
}
for _, c := range g.Coordinates {
@ -91,7 +97,7 @@ func (g Polygon) hasPositions() bool {
// WithinBBox detects if the object is fully contained inside a bbox.
func (g Polygon) WithinBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).InsideRect(rectBBox(bbox))
}
if len(g.Coordinates) == 0 {
@ -110,7 +116,7 @@ func (g Polygon) WithinBBox(bbox BBox) bool {
// IntersectsBBox detects if the object intersects a bbox.
func (g Polygon) IntersectsBBox(bbox BBox) bool {
if g.BBox != nil {
if g.bboxDefined {
return rectBBox(g.CalculatedBBox()).IntersectsRect(rectBBox(bbox))
}
if len(g.Coordinates) == 0 {
@ -178,46 +184,9 @@ func (g Polygon) Nearby(center Position, meters float64) bool {
return nearbyObjectShared(g, center.X, center.Y, meters)
}
// KML outputs kml
func (g Polygon) KML() string {
var buf bytes.Buffer
buf.WriteString(`<?xml version="1.0" encoding="UTF-8"?>`)
buf.WriteString(`<kml xmlns="http://www.opengis.net/kml/2.2">`)
buf.WriteString(`<Placemark>`)
buf.WriteString(`<Polygon>`)
buf.WriteString(`<extrude>1</extrude>`)
buf.WriteString(`<altitudeMode>relativeToGround</altitudeMode>`)
for i, c := range g.Coordinates {
if i == 0 {
buf.WriteString(`<outerBoundaryIs>`)
} else {
buf.WriteString(`<innerBoundaryIs>`)
}
buf.WriteString(`<LinearRing>`)
buf.WriteString(`<coordinates>`)
for _, c := range c {
buf.WriteString("\n" + strconv.FormatFloat(c.X, 'f', -1, 64) + `,` + strconv.FormatFloat(c.Y, 'f', -1, 64) + `,` + strconv.FormatFloat(c.Z, 'f', -1, 64))
}
if len(c) > 0 {
buf.WriteString("\n")
}
buf.WriteString(`</coordinates>`)
buf.WriteString(`</LinearRing>`)
if i == 0 {
buf.WriteString(`</outerBoundaryIs>`)
} else {
buf.WriteString(`</innerBoundaryIs>`)
}
}
buf.WriteString(`</Polygon>`)
buf.WriteString(`</Placemark>`)
buf.WriteString(`</kml>`)
return buf.String()
}
// IsBBoxDefined returns true if the object has a defined bbox.
func (g Polygon) IsBBoxDefined() bool {
return g.BBox != nil
return g.bboxDefined
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.

View File

@ -1,7 +1,6 @@
package geojson
import (
"bytes"
"encoding/binary"
"math"
"strconv"
@ -38,26 +37,15 @@ func polyExteriorHoles(positions [][]Position) (exterior poly.Polygon, holes []p
return
}
func (p Position) writeJSON(buf *bytes.Buffer, isCordZ bool) {
buf.WriteString(strconv.FormatFloat(p.X, 'f', -1, 64))
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(p.Y, 'f', -1, 64))
func appendPositionJSON(json []byte, p Position, isCordZ bool) []byte {
json = strconv.AppendFloat(json, p.X, 'f', -1, 64)
json = append(json, ',')
json = strconv.AppendFloat(json, p.Y, 'f', -1, 64)
if isCordZ {
buf.WriteByte(',')
buf.WriteString(strconv.FormatFloat(p.Z, 'f', -1, 64))
}
}
func (p Position) writeBytes(buf *bytes.Buffer, isCordZ bool) {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, math.Float64bits(p.X))
buf.Write(b)
binary.LittleEndian.PutUint64(b, math.Float64bits(p.Y))
buf.Write(b)
if isCordZ {
binary.LittleEndian.PutUint64(b, math.Float64bits(p.Z))
buf.Write(b)
json = append(json, ',')
json = strconv.AppendFloat(json, p.Z, 'f', -1, 64)
}
return json
}
const earthRadius = 6371e3

View File

@ -51,12 +51,16 @@ func (g SimplePoint) Weight() int {
// MarshalJSON allows the object to be encoded in json.Marshal calls.
func (g SimplePoint) MarshalJSON() ([]byte, error) {
return []byte(g.JSON()), nil
return g.appendJSON(nil), nil
}
func (g SimplePoint) appendJSON(json []byte) []byte {
return appendLevel1JSON(json, "Point", Position{X: g.X, Y: g.Y, Z: 0}, nil, false)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (g SimplePoint) JSON() string {
return level1JSON("Point", Position{X: g.X, Y: g.Y, Z: 0}, nil)
return string(g.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.

View File

@ -45,10 +45,14 @@ func (s String) CalculatedPoint() Position {
return Position{}
}
func (s String) appendJSON(json []byte) []byte {
b, _ := s.MarshalJSON()
return append(json, b...)
}
// JSON is the json representation of the object. This might not be exactly the same as the original.
func (s String) JSON() string {
b, _ := s.MarshalJSON()
return string(b)
return string(s.appendJSON(nil))
}
// String returns a string representation of the object. This might be JSON or something else.
@ -57,7 +61,7 @@ func (s String) String() string {
}
// IsGeometry return true if the object is a geojson geometry object. false if it something else.
func (g String) IsGeometry() bool {
func (s String) IsGeometry() bool {
return false
}