Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
51305: geo,geoindex: use bounding box for geography coverings that are bad r=sumeerbhola a=sumeerbhola

This change uses a covererInterface implementation for geography
that notices when a covering is using top-level cells of all faces
and in that case uses the bounding box to compute the covering.

Also changed the bounding box calculation for geography shapes to
use only the points and not first construct s2.Regions. The latter
causes marginally bad shapes to continue to have bad coverings since
the bounding box also covers the whole earth.

Release note: None

51882: roachpb: panic when comparing a Lease to a non-lease r=andreimatei a=andreimatei

Release note: None

52146: sql: remove local execution of projectSetNode and implement ConstructProjectSet in the new factory r=yuzefovich a=yuzefovich

Depends on #52108.

**sql: remove local execution of projectSetNode**

We have project set processor which is always planned for
`projectSetNode`, so this commit removes the dead code of its local
execution. Additionally, it removes some unused fields and cleans up
cancellation check of the processor.

Release note: None

**sql: implement ConstructProjectSet in the new factory**

Addresses: #47473.

Release note: None

52320: kvserver: enable merges in kvnemesis r=aayushshah15 a=aayushshah15

We had merges disabled because of the bugs tracked in #44878, but those have
since been fixed by #46085 and #50265.

Release note: None

Co-authored-by: sumeerbhola <[email protected]>
Co-authored-by: Andrei Matei <[email protected]>
Co-authored-by: Yahor Yuzefovich <[email protected]>
Co-authored-by: Aayush Shah <[email protected]>
  • Loading branch information
5 people committed Aug 4, 2020
5 parents 986e308 + ddff188 + 0517fde + fe44189 + d58c6a4 commit b380060
Show file tree
Hide file tree
Showing 15 changed files with 276 additions and 281 deletions.
69 changes: 62 additions & 7 deletions pkg/geo/bbox.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,18 +140,73 @@ func BoundingBoxFromGeomTGeometryType(g geom.T) *CartesianBoundingBox {
return bbox
}

// boundingBoxFromGeomTGeographyType returns an appropriate bounding box for a Geography type.
// boundingBoxFromGeomTGeographyType returns an appropriate bounding box for a
// Geography type. There are marginally invalid shapes for which we want
// bounding boxes that are correct regardless of the validity of the shape,
// since validity checks may return slightly different results in S2 and the
// other libraries we use. Therefore, instead of constructing s2.Region(s)
// from the shape, which will expose us to S2's validity checks, we use the
// points and lines directly to compute the bounding box.
func boundingBoxFromGeomTGeographyType(g geom.T) (s2.Rect, error) {
if g.Empty() {
return s2.EmptyRect(), nil
}
regions, err := S2RegionsFromGeomT(g, EmptyBehaviorOmit)
if err != nil {
return s2.EmptyRect(), err
}
rect := s2.EmptyRect()
for _, region := range regions {
rect = rect.Union(region.RectBound())
switch g := g.(type) {
case *geom.Point:
return geogPointsBBox(g), nil
case *geom.MultiPoint:
return geogPointsBBox(g), nil
case *geom.LineString:
return geogLineBBox(g), nil
case *geom.MultiLineString:
for i := 0; i < g.NumLineStrings(); i++ {
rect = rect.Union(geogLineBBox(g.LineString(i)))
}
case *geom.Polygon:
for i := 0; i < g.NumLinearRings(); i++ {
rect = rect.Union(geogLineBBox(g.LinearRing(i)))
}
case *geom.MultiPolygon:
for i := 0; i < g.NumPolygons(); i++ {
polyRect, err := boundingBoxFromGeomTGeographyType(g.Polygon(i))
if err != nil {
return s2.EmptyRect(), err
}
rect = rect.Union(polyRect)
}
case *geom.GeometryCollection:
for i := 0; i < g.NumGeoms(); i++ {
collRect, err := boundingBoxFromGeomTGeographyType(g.Geom(i))
if err != nil {
return s2.EmptyRect(), err
}
rect = rect.Union(collRect)
}
default:
return s2.EmptyRect(), errors.Errorf("unknown type %T", g)
}
return rect, nil
}

// geogPointsBBox constructs a bounding box, represented as a s2.Rect, for the set
// of points contained in g.
func geogPointsBBox(g geom.T) s2.Rect {
rect := s2.EmptyRect()
flatCoords := g.FlatCoords()
for i := 0; i < len(flatCoords); i += g.Stride() {
rect = rect.AddPoint(s2.LatLngFromDegrees(flatCoords[i+1], flatCoords[i]))
}
return rect
}

// geogLineBBox constructs a bounding box, represented as a s2.Rect, for the line
// or ring/loop represented by g.
func geogLineBBox(g geom.T) s2.Rect {
bounder := s2.NewRectBounder()
flatCoords := g.FlatCoords()
for i := 0; i < len(flatCoords); i += g.Stride() {
bounder.AddPoint(s2.PointFromLatLng(s2.LatLngFromDegrees(flatCoords[i+1], flatCoords[i])))
}
return bounder.RectBound()
}
14 changes: 11 additions & 3 deletions pkg/geo/bbox_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,14 +47,22 @@ func TestBoundingBoxFromGeomT(t *testing.T) {

{geopb.SpatialObjectType_GeographyType, geom.NewPointFlat(geom.XY, []float64{-15, -20}), &geopb.BoundingBox{LoX: -0.2617993877991494, LoY: -0.3490658503988659, HiX: -0.2617993877991494, HiY: -0.3490658503988659}},
{geopb.SpatialObjectType_GeographyType, geom.NewPointFlat(geom.XY, []float64{0, 0}), &geopb.BoundingBox{LoX: 0, LoY: 0, HiX: 0, HiY: 0}},
{geopb.SpatialObjectType_GeographyType, testGeomPoint, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.03490658503988659, HiX: 0.017453292519943292, HiY: 0.03490658503988659}},
{geopb.SpatialObjectType_GeographyType, testGeomPoint, &geopb.BoundingBox{LoX: 0.017453292519943295, LoY: 0.03490658503988659, HiX: 0.017453292519943295, HiY: 0.03490658503988659}},
{geopb.SpatialObjectType_GeographyType, testGeomLineString, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.01745329251994285, HiX: 0.03490658503988659, HiY: 0.03490658503988703}},
{geopb.SpatialObjectType_GeographyType, geom.NewLineStringFlat(geom.XY, []float64{-15, -20, -30, -40}), &geopb.BoundingBox{LoX: -0.5235987755982988, LoY: -0.6981317007977321, HiX: -0.2617993877991494, HiY: -0.34906585039886545}},
{geopb.SpatialObjectType_GeographyType, testGeomPolygon, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.01745329251994285, HiX: 0.03490658503988659, HiY: 0.03490791314678354}},
{geopb.SpatialObjectType_GeographyType, testGeomMultiPoint, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.017453292519943295, HiX: 0.03490658503988659, HiY: 0.034906585039886584}},
// Reverse the orientation of testGeomPolygon -- same result.
{geopb.SpatialObjectType_GeographyType, geom.NewPolygonFlat(geom.XY, []float64{1.0, 1.0, 1.0, 2.0, 2.0, 2.0, 1.0, 1.0}, []int{8}),
&geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.01745329251994285, HiX: 0.03490658503988659, HiY: 0.03490791314678354}},
{geopb.SpatialObjectType_GeographyType, geom.NewPolygonFlat(geom.XY, []float64{150, 85, 160, 85, -20, 85, -30, 85, 150, 85}, []int{10}),
&geopb.BoundingBox{LoX: -3.141592653589793, LoY: 1.4835298641951797, HiX: 3.141592653589793, HiY: 1.5707963267948966}},
// Reversed the previous polygon -- same result.
{geopb.SpatialObjectType_GeographyType, geom.NewPolygonFlat(geom.XY, []float64{150, 85, -30, 85, -20, 85, 160, 85, 150, 85}, []int{10}),
&geopb.BoundingBox{LoX: -3.141592653589793, LoY: 1.4835298641951797, HiX: 3.141592653589793, HiY: 1.5707963267948966}},
{geopb.SpatialObjectType_GeographyType, testGeomMultiPoint, &geopb.BoundingBox{LoX: 0.017453292519943295, LoY: 0.017453292519943295, HiX: 0.03490658503988659, HiY: 0.03490658503988659}},
{geopb.SpatialObjectType_GeographyType, testGeomMultiLineString, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.01745329251994285, HiX: 0.06981317007977318, HiY: 0.06981317007977363}},
{geopb.SpatialObjectType_GeographyType, testGeomMultiPolygon, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.01745329251994285, HiX: 0.06981317007977318, HiY: 0.06981581982279463}},
{geopb.SpatialObjectType_GeographyType, testGeomGeometryCollection, &geopb.BoundingBox{LoX: 0.017453292519943292, LoY: 0.017453292519943295, HiX: 0.03490658503988659, HiY: 0.03490658503988659}},
{geopb.SpatialObjectType_GeographyType, testGeomGeometryCollection, &geopb.BoundingBox{LoX: 0.017453292519943295, LoY: 0.017453292519943295, HiX: 0.03490658503988659, HiY: 0.03490658503988659}},
{geopb.SpatialObjectType_GeographyType, emptyGeomPoint, nil},
{geopb.SpatialObjectType_GeographyType, emptyGeomLineString, nil},
{geopb.SpatialObjectType_GeographyType, emptyGeomPolygon, nil},
Expand Down
62 changes: 58 additions & 4 deletions pkg/geo/geoindex/s2_geography_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"github.com/cockroachdb/errors"
"github.com/golang/geo/s1"
"github.com/golang/geo/s2"
"github.com/twpayne/go-geom"
)

// s2GeographyIndex is an implementation of GeographyIndex that uses the S2 geometry
Expand Down Expand Up @@ -53,13 +54,66 @@ func DefaultGeographyIndexConfig() *Config {
}
}

// geogCovererWithBBoxFallback first computes the covering for the provided
// regions (which were computed using g), and if the covering is too broad
// (contains top-level cells from all faces), falls back to using the bounding
// box of g to compute the covering.
type geogCovererWithBBoxFallback struct {
rc *s2.RegionCoverer
g *geo.Geography
}

var _ covererInterface = geogCovererWithBBoxFallback{}

func toDeg(radians float64) float64 {
return s1.Angle(radians).Degrees()
}

func (rc geogCovererWithBBoxFallback) covering(regions []s2.Region) s2.CellUnion {
cu := simpleCovererImpl{rc: rc.rc}.covering(regions)
if isBadGeogCovering(cu) {
bbox := rc.g.SpatialObject().BoundingBox
if bbox == nil {
return cu
}
flatCoords := []float64{
toDeg(bbox.LoX), toDeg(bbox.LoY), toDeg(bbox.HiX), toDeg(bbox.LoY),
toDeg(bbox.HiX), toDeg(bbox.HiY), toDeg(bbox.LoX), toDeg(bbox.HiY),
toDeg(bbox.LoX), toDeg(bbox.LoY)}
bboxT := geom.NewPolygonFlat(geom.XY, flatCoords, []int{len(flatCoords)})
bboxRegions, err := geo.S2RegionsFromGeomT(bboxT, geo.EmptyBehaviorOmit)
if err != nil {
return cu
}
bboxCU := simpleCovererImpl{rc: rc.rc}.covering(bboxRegions)
if !isBadGeogCovering(bboxCU) {
cu = bboxCU
}
}
return cu
}

func isBadGeogCovering(cu s2.CellUnion) bool {
const numFaces = 6
if len(cu) != numFaces {
return false
}
numFaceCells := 0
for _, c := range cu {
if c.Level() == 0 {
numFaceCells++
}
}
return numFaces == numFaceCells
}

// InvertedIndexKeys implements the GeographyIndex interface.
func (i *s2GeographyIndex) InvertedIndexKeys(c context.Context, g *geo.Geography) ([]Key, error) {
r, err := g.AsS2(geo.EmptyBehaviorOmit)
if err != nil {
return nil, err
}
return invertedIndexKeys(c, simpleCovererImpl{rc: i.rc}, r), nil
return invertedIndexKeys(c, geogCovererWithBBoxFallback{rc: i.rc, g: g}, r), nil
}

// Covers implements the GeographyIndex interface.
Expand All @@ -68,7 +122,7 @@ func (i *s2GeographyIndex) Covers(c context.Context, g *geo.Geography) (UnionKey
if err != nil {
return nil, err
}
return covers(c, simpleCovererImpl{rc: i.rc}, r), nil
return covers(c, geogCovererWithBBoxFallback{rc: i.rc, g: g}, r), nil
}

// CoveredBy implements the GeographyIndex interface.
Expand All @@ -86,7 +140,7 @@ func (i *s2GeographyIndex) Intersects(c context.Context, g *geo.Geography) (Unio
if err != nil {
return nil, err
}
return intersects(c, simpleCovererImpl{rc: i.rc}, r), nil
return intersects(c, geogCovererWithBBoxFallback{rc: i.rc, g: g}, r), nil
}

func (i *s2GeographyIndex) DWithin(
Expand All @@ -113,7 +167,7 @@ func (i *s2GeographyIndex) DWithin(
// desire.
//
// Construct the cell covering for the shape.
gCovering := simpleCovererImpl{rc: i.rc}.covering(r)
gCovering := geogCovererWithBBoxFallback{rc: i.rc, g: g}.covering(r)
// Convert the distanceMeters to an angle, in order to expand the cell covering
// on the sphere by the angle.
multiplier := 1.0
Expand Down
18 changes: 9 additions & 9 deletions pkg/geo/geoindex/s2_geometry_index.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,35 +133,35 @@ const exceedsBoundsCellID = s2.CellID(^uint64(0))
// TODO(sumeer): adjust code to handle precision issues with floating point
// arithmetic.

// covererWithBBoxFallback first computes the covering for the provided
// geomCovererWithBBoxFallback first computes the covering for the provided
// regions (which were computed using geom), and if the covering is too
// broad (contains faces other than 0), falls back to using the bounding
// box of geom to compute the covering.
type covererWithBBoxFallback struct {
type geomCovererWithBBoxFallback struct {
s *s2GeometryIndex
geom geom.T
}

var _ covererInterface = covererWithBBoxFallback{}
var _ covererInterface = geomCovererWithBBoxFallback{}

func (rc covererWithBBoxFallback) covering(regions []s2.Region) s2.CellUnion {
func (rc geomCovererWithBBoxFallback) covering(regions []s2.Region) s2.CellUnion {
cu := simpleCovererImpl{rc: rc.s.rc}.covering(regions)
if isBadCovering(cu) {
if isBadGeomCovering(cu) {
bbox := geo.BoundingBoxFromGeomTGeometryType(rc.geom)
flatCoords := []float64{
bbox.LoX, bbox.LoY, bbox.HiX, bbox.LoY, bbox.HiX, bbox.HiY, bbox.LoX, bbox.HiY,
bbox.LoX, bbox.LoY}
bboxT := geom.NewPolygonFlat(geom.XY, flatCoords, []int{len(flatCoords)})
bboxRegions := rc.s.s2RegionsFromPlanarGeomT(bboxT)
bboxCU := simpleCovererImpl{rc: rc.s.rc}.covering(bboxRegions)
if !isBadCovering(bboxCU) {
if !isBadGeomCovering(bboxCU) {
cu = bboxCU
}
}
return cu
}

func isBadCovering(cu s2.CellUnion) bool {
func isBadGeomCovering(cu s2.CellUnion) bool {
for _, c := range cu {
if c.Face() != 0 {
// Good coverings should not see a face other than 0.
Expand All @@ -184,7 +184,7 @@ func (s *s2GeometryIndex) InvertedIndexKeys(c context.Context, g *geo.Geometry)
var keys []Key
if gt != nil {
r := s.s2RegionsFromPlanarGeomT(gt)
keys = invertedIndexKeys(c, covererWithBBoxFallback{s: s, geom: gt}, r)
keys = invertedIndexKeys(c, geomCovererWithBBoxFallback{s: s, geom: gt}, r)
}
if clipped {
keys = append(keys, Key(exceedsBoundsCellID))
Expand Down Expand Up @@ -231,7 +231,7 @@ func (s *s2GeometryIndex) Intersects(c context.Context, g *geo.Geometry) (UnionK
var spans UnionKeySpans
if gt != nil {
r := s.s2RegionsFromPlanarGeomT(gt)
spans = intersects(c, covererWithBBoxFallback{s: s, geom: gt}, r)
spans = intersects(c, geomCovererWithBBoxFallback{s: s, geom: gt}, r)
}
if clipped {
// And lookup all shapes that exceed the bounds. The exceedsBoundsCellID is the largest
Expand Down
77 changes: 47 additions & 30 deletions pkg/geo/geoindex/testdata/s2_geography
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,27 @@ POLYGON((-79.7624 42.5142,-79.0672 42.7783,-78.9313 42.8508,-78.9024 42.9061,-78
----

geometry name=nys-approx1
POLYGON((-79 42,-77 44,-78 43,-79 42))
POLYGON((-79 42,-77 44,-78 41,-79 42))
----

geometry name=nys-approx2
POLYGON((-79 42,-78 43,-77 44,-79 42))
POLYGON((-79 42,-78 41,-77 44,-79 42))
----

geometry name=red-hook-nyc
MULTIPOLYGON(((-74.0126927094665 40.6600480732464,-74.0127064547618 40.6600447465578,-74.0141319802258 40.6609147092529,-74.0145335611922 40.6611344232761,-74.0145358849763 40.6611361779097,-74.0145399285567 40.6611402822267,-74.014541648353 40.6611426319101,-74.0145797021934 40.6612147162775,-74.0145806405216 40.6612173067132,-74.0145817746666 40.661222645325,-74.0145819704835 40.6612253935011,-74.0145789529984 40.6612703205022,-74.0145775882448 40.6612747317321,-74.0145730223663 40.6612824707693,-74.0145698212414 40.6612857985766,-74.0144997485308 40.6613308932157,-74.0144993922351 40.6613311121095,-74.0144986708037 40.6613315348099,-74.014498305668 40.6613317386166,-74.0136647221644 40.6617744175344,-74.0134542384902 40.6618992353571,-74.0134540575192 40.6618993401319,-74.0134536934255 40.6618995458745,-74.013453510303 40.6618996468423,-74.0128195031462 40.6622406226638,-74.012816235274 40.6622417071219,-74.0128095254169 40.6622427033898,-74.0128060834319 40.6622426151996,-74.0127019801943 40.6622216604417,-74.012700315079 40.6622211716785,-74.0126970944194 40.6622199136429,-74.012695538875 40.6622191443704,-74.011062576148 40.6612266089193,-74.0110624213581 40.6612218066015,-74.0127057329214 40.6600718294846,-74.0127094072378 40.6600602461869,-74.0127040330277 40.6600453326751,-74.0126927094665 40.6600480732464)))
----

init minlevel=0 maxlevel=30 maxcells=2
----

index-keys name=nys-approx1
----
F4/L11/10321223000, F4/L5/10322

# TODO(sumeer): the IsLinearRingCCW() method returns false for both nys-approx1
# and nys-approx2, which is why the following looks like a polygon that
# encompasses the whole world minus nys. Fix.
F4/L5/10321, F4/L5/10322

index-keys name=nys-approx2
----
F0/L0/, F1/L0/, F2/L0/, F3/L0/, F4/L0/, F5/L0/
F4/L5/10321, F4/L5/10322

index-keys name=point1
----
Expand Down Expand Up @@ -167,25 +167,34 @@ F4/L2/10, F4/L1/1, F4/L0/

index-keys name=nystate
----
F0/L0/, F1/L0/, F2/L0/, F3/L0/, F4/L0/, F5/L0/
F2/L2/12, F4/L2/10

covers name=nystate
----
[F0/L30/000000000000000000000000000000, F0/L30/333333333333333333333333333333],
[F1/L30/000000000000000000000000000000, F1/L30/333333333333333333333333333333],
[F2/L30/000000000000000000000000000000, F2/L30/333333333333333333333333333333],
[F3/L30/000000000000000000000000000000, F3/L30/333333333333333333333333333333],
[F4/L30/000000000000000000000000000000, F4/L30/333333333333333333333333333333],
[F5/L30/000000000000000000000000000000, F5/L30/333333333333333333333333333333]
F2/L1/1, [F2/L30/120000000000000000000000000000, F2/L30/123333333333333333333333333333],
F2/L0/, [F4/L30/100000000000000000000000000000, F4/L30/103333333333333333333333333333],
F4/L1/1, F4/L0/

intersects name=nystate
----
[F0/L30/000000000000000000000000000000, F0/L30/333333333333333333333333333333],
[F1/L30/000000000000000000000000000000, F1/L30/333333333333333333333333333333],
[F2/L30/000000000000000000000000000000, F2/L30/333333333333333333333333333333],
[F3/L30/000000000000000000000000000000, F3/L30/333333333333333333333333333333],
[F4/L30/000000000000000000000000000000, F4/L30/333333333333333333333333333333],
[F5/L30/000000000000000000000000000000, F5/L30/333333333333333333333333333333]
F2/L1/1, [F2/L30/120000000000000000000000000000, F2/L30/123333333333333333333333333333],
F2/L0/, [F4/L30/100000000000000000000000000000, F4/L30/103333333333333333333333333333],
F4/L1/1, F4/L0/

index-keys name=red-hook-nyc
----
F4/L13/1032010231102, F4/L13/1032010231113

init minlevel=0 maxlevel=30 maxcells=8
----

index-keys name=nystate
----
F2/L5/12112, F2/L5/12121, F2/L5/12122, F2/L7/1221111, F4/L4/1001, F4/L4/1032, F4/L5/10330, F4/L5/10331

index-keys name=red-hook-nyc
----
F4/L16/1032010231102232, F4/L16/1032010231102233, F4/L15/103201023110230, F4/L17/10320102311132301, F4/L18/103201023111323020, F4/L16/1032010231113233, F4/L17/10320102311133000, F4/L17/10320102311133003

init minlevel=0 maxlevel=30 maxcells=1
----
Expand Down Expand Up @@ -373,14 +382,12 @@ F0/L2/20, F0/L1/2

index-keys name=nys-approx1
----
F4/L11/10321223000, F4/L5/10322
F4/L5/10321, F4/L5/10322

intersects name=nys-approx1
----
F4/L2/10, F4/L3/103, F4/L5/10321, F4/L6/103212, F4/L7/1032122,
[F4/L30/103212230000000000000000000000, F4/L30/103212230003333333333333333333],
F4/L10/1032122300, F4/L9/103212230, F4/L8/10321223, F4/L4/1032,
[F4/L30/103220000000000000000000000000, F4/L30/103223333333333333333333333333],
F4/L2/10, F4/L3/103, [F4/L30/103210000000000000000000000000, F4/L30/103213333333333333333333333333],
F4/L4/1032, [F4/L30/103220000000000000000000000000, F4/L30/103223333333333333333333333333],
F4/L1/1, F4/L0/

d-within distance=2000 name=nys-approx1
Expand All @@ -397,11 +404,21 @@ F4/L5/10011, [F4/L30/100112100000000000000000000000, F4/L30/10011213333333333333
F4/L6/100112, [F4/L30/100112200000000000000000000000, F4/L30/100112233333333333333333333333],
F4/L4/1001, [F4/L30/100121100000000000000000000000, F4/L30/100121133333333333333333333333],
F4/L6/100121, [F4/L30/100121200000000000000000000000, F4/L30/100121233333333333333333333333],
F4/L5/10012, F4/L3/100, F4/L2/10, F4/L3/103, F4/L5/10320, F4/L6/103202,
F4/L5/10012, [F4/L30/100122100000000000000000000000, F4/L30/100122133333333333333333333333],
F4/L6/100122, [F4/L30/100122200000000000000000000000, F4/L30/100122233333333333333333333333],
F4/L3/100, [F4/L30/100211100000000000000000000000, F4/L30/100211133333333333333333333333],
F4/L6/100211, F4/L5/10021, F4/L4/1002, F4/L2/10, F4/L4/1031,
F4/L5/10312, F4/L6/103122, [F4/L30/103122200000000000000000000000, F4/L30/103122233333333333333333333333],
[F4/L30/103122300000000000000000000000, F4/L30/103122333333333333333333333333],
[F4/L30/103123000000000000000000000000, F4/L30/103123033333333333333333333333],
F4/L6/103123, [F4/L30/103123300000000000000000000000, F4/L30/103123333333333333333333333333],
[F4/L30/103130000000000000000000000000, F4/L30/103130033333333333333333333333],
F4/L6/103130, F4/L5/10313, F4/L3/103, F4/L5/10320, F4/L6/103202,
[F4/L30/103202200000000000000000000000, F4/L30/103202233333333333333333333333],
F4/L5/10321, [F4/L30/103212000000000000000000000000, F4/L30/103212333333333333333333333333],
[F4/L30/103213000000000000000000000000, F4/L30/103213033333333333333333333333],
F4/L6/103213, [F4/L30/103213300000000000000000000000, F4/L30/103213333333333333333333333333],
[F4/L30/103202300000000000000000000000, F4/L30/103202333333333333333333333333],
[F4/L30/103203000000000000000000000000, F4/L30/103203033333333333333333333333],
F4/L6/103203, [F4/L30/103203300000000000000000000000, F4/L30/103203333333333333333333333333],
[F4/L30/103210000000000000000000000000, F4/L30/103213333333333333333333333333],
F4/L4/1032, [F4/L30/103220000000000000000000000000, F4/L30/103223333333333333333333333333],
[F4/L30/103230000000000000000000000000, F4/L30/103230033333333333333333333333],
F4/L6/103230, [F4/L30/103230300000000000000000000000, F4/L30/103230333333333333333333333333],
Expand Down
Loading

0 comments on commit b380060

Please sign in to comment.