mirror of https://github.com/tidwall/tile38.git
Add CLIP subcommand to INTERSECTS
This commit is contained in:
parent
d66abaccb0
commit
2be4eab627
|
@ -497,9 +497,13 @@ func (c *Collection) Within(sparse uint8, obj geojson.Object, minLat, minLon, ma
|
||||||
}
|
}
|
||||||
|
|
||||||
// Intersects returns all object that are intersect an object or bounding box. Set obj to nil in order to use the bounding box.
|
// Intersects returns all object that are intersect an object or bounding box. Set obj to nil in order to use the bounding box.
|
||||||
func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon, maxLat, maxLon, lat, lon, meters, minZ, maxZ float64, iterator func(id string, obj geojson.Object, fields []float64) bool) bool {
|
func (c *Collection) Intersects(
|
||||||
|
sparse uint8, obj geojson.Object,
|
||||||
|
minLat, minLon, maxLat, maxLon, lat, lon, meters, minZ, maxZ float64, doClip bool,
|
||||||
|
iterator func(id string, obj geojson.Object, fields []float64, clipBox geojson.BBox) bool) bool {
|
||||||
|
|
||||||
var keepon = true
|
var keepon = true
|
||||||
var bbox geojson.BBox
|
var clipbox, bbox geojson.BBox
|
||||||
center := geojson.Position{X: lon, Y: lat, Z: 0}
|
center := geojson.Position{X: lon, Y: lat, Z: 0}
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
bbox = obj.CalculatedBBox()
|
bbox = obj.CalculatedBBox()
|
||||||
|
@ -513,6 +517,9 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
|
||||||
bbox = geojson.BBoxesFromCenter(lat, lon, meters)
|
bbox = geojson.BBoxesFromCenter(lat, lon, meters)
|
||||||
} else {
|
} else {
|
||||||
bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: minZ}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: maxZ}}
|
bbox = geojson.BBox{Min: geojson.Position{X: minLon, Y: minLat, Z: minZ}, Max: geojson.Position{X: maxLon, Y: maxLat, Z: maxZ}}
|
||||||
|
if doClip {
|
||||||
|
clipbox = bbox
|
||||||
|
}
|
||||||
}
|
}
|
||||||
var bboxes []geojson.BBox
|
var bboxes []geojson.BBox
|
||||||
if sparse > 0 {
|
if sparse > 0 {
|
||||||
|
@ -531,7 +538,7 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if o.Intersects(obj) {
|
if o.Intersects(obj) {
|
||||||
if iterator(id, o, fields) {
|
if iterator(id, o, fields, clipbox) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -540,7 +547,7 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
|
||||||
} else if meters != -1 {
|
} else if meters != -1 {
|
||||||
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if o.IntersectsCircle(center, meters) {
|
if o.IntersectsCircle(center, meters) {
|
||||||
if iterator(id, o, fields) {
|
if iterator(id, o, fields, clipbox) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -550,7 +557,7 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
|
||||||
if keepon {
|
if keepon {
|
||||||
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
keepon = c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if o.IntersectsBBox(bbox) {
|
if o.IntersectsBBox(bbox) {
|
||||||
if iterator(id, o, fields) {
|
if iterator(id, o, fields, clipbox) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -566,21 +573,21 @@ func (c *Collection) Intersects(sparse uint8, obj geojson.Object, minLat, minLon
|
||||||
if obj != nil {
|
if obj != nil {
|
||||||
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if o.Intersects(obj) {
|
if o.Intersects(obj) {
|
||||||
return iterator(id, o, fields)
|
return iterator(id, o, fields, clipbox)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
} else if meters != -1 {
|
} else if meters != -1 {
|
||||||
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if o.IntersectsCircle(center, meters) {
|
if o.IntersectsCircle(center, meters) {
|
||||||
return iterator(id, o, fields)
|
return iterator(id, o, fields, clipbox)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
return c.geoSearch(bbox, func(id string, o geojson.Object, fields []float64) bool {
|
||||||
if o.IntersectsBBox(bbox) {
|
if o.IntersectsBBox(bbox) {
|
||||||
return iterator(id, o, fields)
|
return iterator(id, o, fields, clipbox)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
|
|
@ -65,6 +65,8 @@ type ScanWriterParams struct {
|
||||||
distance float64
|
distance float64
|
||||||
noLock bool
|
noLock bool
|
||||||
ignoreGlobMatch bool
|
ignoreGlobMatch bool
|
||||||
|
clip bool
|
||||||
|
clipbox geojson.BBox
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) newScanWriter(
|
func (c *Controller) newScanWriter(
|
||||||
|
@ -342,6 +344,9 @@ func (sw *scanWriter) writeObject(opts ScanWriterParams) bool {
|
||||||
if sw.output == outputCount {
|
if sw.output == outputCount {
|
||||||
return sw.count < sw.limit
|
return sw.count < sw.limit
|
||||||
}
|
}
|
||||||
|
if opts.clip {
|
||||||
|
opts.o = opts.o.Clipped(opts.clipbox)
|
||||||
|
}
|
||||||
switch sw.msg.OutputType {
|
switch sw.msg.OutputType {
|
||||||
case server.JSON:
|
case server.JSON:
|
||||||
var wr bytes.Buffer
|
var wr bytes.Buffer
|
||||||
|
|
|
@ -100,6 +100,10 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.clip {
|
||||||
|
err = errInvalidArgument("cannnot clip with point")
|
||||||
|
}
|
||||||
|
|
||||||
umeters := true
|
umeters := true
|
||||||
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
if vs, smeters, ok = tokenval(vs); !ok || smeters == "" {
|
||||||
umeters = false
|
umeters = false
|
||||||
|
@ -134,6 +138,10 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case "circle":
|
case "circle":
|
||||||
|
if s.clip {
|
||||||
|
err = errInvalidArgument("cannnot clip with circle")
|
||||||
|
}
|
||||||
|
|
||||||
var slat, slon, smeters string
|
var slat, slon, smeters string
|
||||||
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
if vs, slat, ok = tokenval(vs); !ok || slat == "" {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
|
@ -165,6 +173,10 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "object":
|
case "object":
|
||||||
|
if s.clip {
|
||||||
|
err = errInvalidArgument("cannnot clip with object")
|
||||||
|
}
|
||||||
|
|
||||||
var obj string
|
var obj string
|
||||||
if vs, obj, ok = tokenval(vs); !ok || obj == "" {
|
if vs, obj, ok = tokenval(vs); !ok || obj == "" {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
|
@ -258,6 +270,9 @@ func (c *Controller) cmdSearchArgs(cmd string, vs []resp.Value, types []string)
|
||||||
}
|
}
|
||||||
s.minLat, s.minLon, s.maxLat, s.maxLon = bing.TileXYToBounds(x, y, z)
|
s.minLat, s.minLon, s.maxLat, s.maxLon = bing.TileXYToBounds(x, y, z)
|
||||||
case "get":
|
case "get":
|
||||||
|
if s.clip {
|
||||||
|
err = errInvalidArgument("cannnot clip with get")
|
||||||
|
}
|
||||||
var key, id string
|
var key, id string
|
||||||
if vs, key, ok = tokenval(vs); !ok || key == "" {
|
if vs, key, ok = tokenval(vs); !ok || key == "" {
|
||||||
err = errInvalidNumberOfArguments
|
err = errInvalidNumberOfArguments
|
||||||
|
@ -512,7 +527,8 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
||||||
s.minLat, s.minLon, s.maxLat, s.maxLon,
|
s.minLat, s.minLon, s.maxLat, s.maxLon,
|
||||||
s.lat, s.lon, s.meters,
|
s.lat, s.lon, s.meters,
|
||||||
minZ, maxZ,
|
minZ, maxZ,
|
||||||
func(id string, o geojson.Object, fields []float64) bool {
|
s.clip,
|
||||||
|
func(id string, o geojson.Object, fields []float64, clipbox geojson.BBox) bool {
|
||||||
if c.hasExpired(s.key, id) {
|
if c.hasExpired(s.key, id) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -521,6 +537,8 @@ func (c *Controller) cmdWithinOrIntersects(cmd string, msg *server.Message) (res
|
||||||
o: o,
|
o: o,
|
||||||
fields: fields,
|
fields: fields,
|
||||||
noLock: true,
|
noLock: true,
|
||||||
|
clip: s.clip,
|
||||||
|
clipbox: clipbox,
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
@ -245,6 +245,7 @@ type searchScanBaseTokens struct {
|
||||||
usparse bool
|
usparse bool
|
||||||
sparse uint8
|
sparse uint8
|
||||||
desc bool
|
desc bool
|
||||||
|
clip bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value, t searchScanBaseTokens, err error) {
|
func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vsout []resp.Value, t searchScanBaseTokens, err error) {
|
||||||
|
@ -551,6 +552,14 @@ func (c *Controller) parseSearchScanBaseTokens(cmd string, vs []resp.Value) (vso
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
} else if (wtok[0] == 'C' || wtok[0] == 'c') && strings.ToLower(wtok) == "clip" {
|
||||||
|
vs = nvs
|
||||||
|
if t.clip {
|
||||||
|
err = errDuplicateArgument(strings.ToUpper(wtok))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
t.clip = true
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
|
|
@ -957,6 +957,12 @@
|
||||||
"multiple": true,
|
"multiple": true,
|
||||||
"variadic": true
|
"variadic": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"command": "CLIP",
|
||||||
|
"name": [],
|
||||||
|
"type": [],
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"command": "NOFIELDS",
|
"command": "NOFIELDS",
|
||||||
"name": [],
|
"name": [],
|
||||||
|
|
|
@ -0,0 +1,121 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
// Cohen-Sutherland Line Clipping
|
||||||
|
// https://www.cs.helsinki.fi/group/goa/viewing/leikkaus/lineClip.html
|
||||||
|
func ClipSegment(start, end Position, bbox BBox) (resStart, resEnd Position, rejected bool) {
|
||||||
|
startCode := getCode(bbox, start)
|
||||||
|
endCode := getCode(bbox, end)
|
||||||
|
|
||||||
|
if (startCode | endCode) == 0 {
|
||||||
|
// trivially accept
|
||||||
|
resStart, resEnd = start, end
|
||||||
|
} else if (startCode & endCode) != 0 {
|
||||||
|
// trivially reject
|
||||||
|
rejected = true
|
||||||
|
} else if startCode != 0 {
|
||||||
|
// start is outside. get new start.
|
||||||
|
newStart := intersect(bbox, startCode, start, end)
|
||||||
|
resStart, resEnd, rejected = ClipSegment(newStart, end, bbox)
|
||||||
|
} else {
|
||||||
|
// end is outside. get new end.
|
||||||
|
newEnd := intersect(bbox, endCode, start, end)
|
||||||
|
resStart, resEnd, rejected = ClipSegment(start, newEnd, bbox)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sutherland-Hodgman Polygon Clipping
|
||||||
|
// https://www.cs.helsinki.fi/group/goa/viewing/leikkaus/intro2.html
|
||||||
|
func ClipRing(ring[] Position, bbox BBox) (resRing []Position) {
|
||||||
|
|
||||||
|
if len(ring) < 4 {
|
||||||
|
// under 4 elements this is not a polygon ring!
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var edge uint8
|
||||||
|
var inside, prevInside bool
|
||||||
|
var prev Position
|
||||||
|
|
||||||
|
for edge = 1; edge <= 8; edge *=2 {
|
||||||
|
prev = ring[len(ring) - 2]
|
||||||
|
prevInside = (getCode(bbox, prev) & edge) == 0
|
||||||
|
|
||||||
|
for _, p := range ring {
|
||||||
|
|
||||||
|
inside = (getCode(bbox, p) & edge) == 0
|
||||||
|
|
||||||
|
if prevInside && inside {
|
||||||
|
// Staying inside
|
||||||
|
resRing = append(resRing, p)
|
||||||
|
} else if prevInside && !inside {
|
||||||
|
// Leaving
|
||||||
|
resRing = append(resRing, intersect(bbox, edge, prev, p))
|
||||||
|
} else if !prevInside && inside {
|
||||||
|
// Entering
|
||||||
|
resRing = append(resRing, intersect(bbox, edge, prev, p))
|
||||||
|
resRing = append(resRing, p)
|
||||||
|
} else {
|
||||||
|
// Staying outside
|
||||||
|
}
|
||||||
|
|
||||||
|
prev, prevInside = p, inside
|
||||||
|
}
|
||||||
|
|
||||||
|
if resRing[0] != resRing[len(resRing)-1] {
|
||||||
|
resRing = append(resRing, resRing[0])
|
||||||
|
}
|
||||||
|
ring, resRing = resRing, []Position{}
|
||||||
|
}
|
||||||
|
|
||||||
|
resRing = ring
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func getCode(bbox BBox, point Position) (code uint8) {
|
||||||
|
code = 0
|
||||||
|
|
||||||
|
if point.X < bbox.Min.X {
|
||||||
|
code |= 1 // left
|
||||||
|
} else if point.X > bbox.Max.X {
|
||||||
|
code |= 2 // right
|
||||||
|
}
|
||||||
|
|
||||||
|
if point.Y < bbox.Min.Y {
|
||||||
|
code |= 4 // bottom
|
||||||
|
} else if point.Y > bbox.Max.Y {
|
||||||
|
code |= 8 // top
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func intersect(bbox BBox, code uint8, start, end Position) (new Position) {
|
||||||
|
if (code & 8) != 0 { // top
|
||||||
|
new = Position{
|
||||||
|
X: start.X + (end.X - start.X) * (bbox.Max.Y - start.Y) / (end.Y - start.Y),
|
||||||
|
Y: bbox.Max.Y,
|
||||||
|
}
|
||||||
|
} else if (code & 4) != 0 { // bottom
|
||||||
|
new = Position{
|
||||||
|
X: start.X + (end.X - start.X) * (bbox.Min.Y - start.Y) / (end.Y - start.Y),
|
||||||
|
Y: bbox.Min.Y,
|
||||||
|
}
|
||||||
|
} else if (code & 2) != 0 { //right
|
||||||
|
new = Position{
|
||||||
|
X: bbox.Max.X,
|
||||||
|
Y: start.Y + (end.Y - start.Y) * (bbox.Max.X - start.X) / (end.X - start.X),
|
||||||
|
}
|
||||||
|
} else if (code & 1) != 0 { // left
|
||||||
|
new = Position{
|
||||||
|
X: bbox.Min.X,
|
||||||
|
Y: start.Y + (end.Y - start.Y) * (bbox.Min.X - start.X) / (end.X - start.X),
|
||||||
|
}
|
||||||
|
} else { // should not call intersect with the zero code
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
package geojson
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestClipLineString(t *testing.T) {
|
||||||
|
ls, _ := fillLineString(
|
||||||
|
[]Position{
|
||||||
|
{X: 1, Y: 1},
|
||||||
|
{X: 2, Y: 2},
|
||||||
|
{X: 3, Y: 1},
|
||||||
|
}, nil, nil)
|
||||||
|
bbox := BBox{
|
||||||
|
Min: Position{X: 1.5, Y: 0.5},
|
||||||
|
Max: Position{X: 2.5, Y: 1.8},
|
||||||
|
}
|
||||||
|
clipped := ls.Clipped(bbox)
|
||||||
|
cl, ok := clipped.(MultiLineString)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("wrong type")
|
||||||
|
}
|
||||||
|
if len(cl.Coordinates) != 2 {
|
||||||
|
t.Fatal("result must have two parts in MultiString")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func TestClipPolygon(t *testing.T) {
|
||||||
|
outer := []Position{
|
||||||
|
{X: 2, Y: 2},
|
||||||
|
{X: 1, Y: 2},
|
||||||
|
{X: 1.5, Y: 1.5},
|
||||||
|
{X: 1, Y: 1},
|
||||||
|
{X: 2, Y: 1},
|
||||||
|
{X: 2, Y: 2},
|
||||||
|
}
|
||||||
|
inner := []Position{
|
||||||
|
{X: 1.9, Y: 1.9},
|
||||||
|
{X: 1.2, Y: 1.9},
|
||||||
|
{X: 1.45, Y: 1.65},
|
||||||
|
{X: 1.9, Y: 1.5},
|
||||||
|
{X: 1.9, Y: 1.9},
|
||||||
|
|
||||||
|
}
|
||||||
|
polygon, _ := fillPolygon([][]Position{outer, inner}, nil, nil)
|
||||||
|
bbox := BBox{
|
||||||
|
Min: Position{X: 1.3, Y: 1.3},
|
||||||
|
Max: Position{X: 1.4, Y: 2.15},
|
||||||
|
}
|
||||||
|
clipped := polygon.Clipped(bbox)
|
||||||
|
cp, ok := clipped.(Polygon)
|
||||||
|
if !ok {
|
||||||
|
t.Fatal("wrong type")
|
||||||
|
}
|
||||||
|
if len(cp.Coordinates) != 2 {
|
||||||
|
t.Fatal("result must have two parts in Polygon")
|
||||||
|
}
|
||||||
|
}
|
|
@ -232,3 +232,14 @@ func (g Feature) IsBBoxDefined() bool {
|
||||||
func (g Feature) IsGeometry() bool {
|
func (g Feature) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g Feature) Clipped(bbox BBox) Object {
|
||||||
|
clippedGeometry := g.Geometry.Clipped(bbox)
|
||||||
|
|
||||||
|
res := Feature{Geometry: clippedGeometry, idprops: g.idprops}
|
||||||
|
cbbox := clippedGeometry.CalculatedBBox()
|
||||||
|
res.BBox = &cbbox
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -248,3 +248,16 @@ func (g FeatureCollection) IsBBoxDefined() bool {
|
||||||
func (g FeatureCollection) IsGeometry() bool {
|
func (g FeatureCollection) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g FeatureCollection) Clipped(bbox BBox) Object {
|
||||||
|
var new_features []Object
|
||||||
|
for _, feature := range g.Features {
|
||||||
|
new_features = append(new_features, feature.Clipped(bbox))
|
||||||
|
}
|
||||||
|
|
||||||
|
fc := FeatureCollection{Features: new_features}
|
||||||
|
cbbox := fc.CalculatedBBox()
|
||||||
|
fc.BBox = &cbbox
|
||||||
|
return fc
|
||||||
|
}
|
||||||
|
|
|
@ -246,3 +246,16 @@ func (g GeometryCollection) IsBBoxDefined() bool {
|
||||||
func (g GeometryCollection) IsGeometry() bool {
|
func (g GeometryCollection) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object of the same type as this object, clipped by a bbox.
|
||||||
|
func (g GeometryCollection) Clipped(bbox BBox) Object {
|
||||||
|
var new_geometries []Object
|
||||||
|
for _, geometry := range g.Geometries {
|
||||||
|
new_geometries = append(new_geometries, geometry.Clipped(bbox))
|
||||||
|
}
|
||||||
|
|
||||||
|
gc := GeometryCollection{Geometries: new_geometries}
|
||||||
|
cbbox := gc.CalculatedBBox()
|
||||||
|
gc.BBox = &cbbox
|
||||||
|
return gc
|
||||||
|
}
|
||||||
|
|
|
@ -151,3 +151,31 @@ func (g LineString) IsBBoxDefined() bool {
|
||||||
func (g LineString) IsGeometry() bool {
|
func (g LineString) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g LineString) Clipped(bbox BBox) Object {
|
||||||
|
var new_coordinates [][]Position
|
||||||
|
var clipedStart, clippedEnd Position
|
||||||
|
var rejected bool
|
||||||
|
var line []Position
|
||||||
|
|
||||||
|
for i := 0; i < len(g.Coordinates) - 1 ; i++ {
|
||||||
|
clipedStart, clippedEnd, rejected = ClipSegment(g.Coordinates[i], g.Coordinates[i + 1], bbox)
|
||||||
|
if rejected {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if len(line) > 0 && line[len(line) - 1] != clipedStart {
|
||||||
|
new_coordinates = append(new_coordinates, line)
|
||||||
|
line = []Position{clipedStart}
|
||||||
|
} else if len(line) == 0 {
|
||||||
|
line = append(line, clipedStart)
|
||||||
|
}
|
||||||
|
line = append(line, clippedEnd)
|
||||||
|
}
|
||||||
|
if len(line) > 0 {
|
||||||
|
new_coordinates = append(new_coordinates, line)
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := fillMultiLineString(new_coordinates, nil, nil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -209,3 +209,18 @@ func (g MultiLineString) IsBBoxDefined() bool {
|
||||||
func (g MultiLineString) IsGeometry() bool {
|
func (g MultiLineString) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g MultiLineString) Clipped(bbox BBox) Object {
|
||||||
|
var new_coordinates [][]Position
|
||||||
|
|
||||||
|
for ix := range g.Coordinates {
|
||||||
|
clippedMultiLineString, _ := g.getLineString(ix).Clipped(bbox).(MultiLineString)
|
||||||
|
for _, ls := range clippedMultiLineString.Coordinates {
|
||||||
|
new_coordinates = append(new_coordinates, ls)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := fillMultiLineString(new_coordinates, nil, nil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -177,3 +177,18 @@ func (g MultiPoint) IsBBoxDefined() bool {
|
||||||
func (g MultiPoint) IsGeometry() bool {
|
func (g MultiPoint) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g MultiPoint) Clipped(bbox BBox) Object {
|
||||||
|
var new_coordinates []Position
|
||||||
|
|
||||||
|
for _, position := range g.Coordinates {
|
||||||
|
if poly.Point(position).InsideRect(rectBBox(bbox)) {
|
||||||
|
new_coordinates = append(new_coordinates, position)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := fillMultiPoint(new_coordinates, nil, nil)
|
||||||
|
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -215,3 +215,18 @@ func (g MultiPolygon) IsBBoxDefined() bool {
|
||||||
func (g MultiPolygon) IsGeometry() bool {
|
func (g MultiPolygon) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g MultiPolygon) Clipped(bbox BBox) Object {
|
||||||
|
var new_coordinates [][][]Position
|
||||||
|
|
||||||
|
for _, polygon := range g.polygons {
|
||||||
|
clippedMultiPolygon, _ := polygon.Clipped(bbox).(MultiPolygon)
|
||||||
|
for _, pg := range clippedMultiPolygon.Coordinates {
|
||||||
|
new_coordinates = append(new_coordinates, pg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := fillMultiPolygon(new_coordinates, nil, nil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -94,6 +94,8 @@ type Object interface {
|
||||||
IsBBoxDefined() bool
|
IsBBoxDefined() bool
|
||||||
// IsGeometry return true if the object is a geojson geometry object. false if it something else.
|
// IsGeometry return true if the object is a geojson geometry object. false if it something else.
|
||||||
IsGeometry() bool
|
IsGeometry() bool
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
Clipped(bbox BBox) Object
|
||||||
}
|
}
|
||||||
|
|
||||||
func positionBBox(i int, bbox BBox, ps []Position) (int, BBox) {
|
func positionBBox(i int, bbox BBox, ps []Position) (int, BBox) {
|
||||||
|
|
|
@ -145,3 +145,13 @@ func (g Point) IsBBoxDefined() bool {
|
||||||
func (g Point) IsGeometry() bool {
|
func (g Point) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g Point) Clipped(bbox BBox) Object {
|
||||||
|
if g.IntersectsBBox(bbox) {
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := fillMultiPoint([]Position{}, nil, nil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -209,3 +209,15 @@ func (g Polygon) IsBBoxDefined() bool {
|
||||||
func (g Polygon) IsGeometry() bool {
|
func (g Polygon) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g Polygon) Clipped(bbox BBox) Object {
|
||||||
|
var new_coordinates [][]Position
|
||||||
|
|
||||||
|
for _, ring := range g.Coordinates {
|
||||||
|
new_coordinates = append(new_coordinates, ClipRing(ring, bbox))
|
||||||
|
}
|
||||||
|
|
||||||
|
res, _ := fillPolygon(new_coordinates, nil, nil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -127,3 +127,12 @@ func (g SimplePoint) IsBBoxDefined() bool {
|
||||||
func (g SimplePoint) IsGeometry() bool {
|
func (g SimplePoint) IsGeometry() bool {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (g SimplePoint) Clipped(bbox BBox) Object {
|
||||||
|
if g.IntersectsBBox(bbox) {
|
||||||
|
return g
|
||||||
|
}
|
||||||
|
res, _ := fillMultiPoint([]Position{}, nil, nil)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
|
@ -73,6 +73,11 @@ func (s String) IsGeometry() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clip returns the object obtained by clipping this object by a bbox.
|
||||||
|
func (s String) Clipped(bbox BBox) Object {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
// Bytes is the bytes representation of the object.
|
// Bytes is the bytes representation of the object.
|
||||||
func (s String) Bytes() []byte {
|
func (s String) Bytes() []byte {
|
||||||
return []byte(s.String())
|
return []byte(s.String())
|
||||||
|
|
Loading…
Reference in New Issue