zeroshade commented on code in PR #545: URL: https://github.com/apache/arrow-go/pull/545#discussion_r2461942047
########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) Review Comment: what would be the alternate to this that would be the other constants? ########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) + +// String returns the string representation of the encoding +func (e GeometryEncoding) String() string { + switch e { + case EncodingGeoArrow: + return "geoarrow" + default: + return "unknown" + } +} + +// EdgeType represents the edge interpretation for geometry data +type EdgeType string + +const ( + EdgePlanar EdgeType = "planar" + EdgeSpherical EdgeType = "spherical" +) + +// String returns the string representation of the edge type +func (e EdgeType) String() string { + return string(e) +} + +// CoordType represents the coordinate layout type +type CoordType string + +const ( + CoordSeparate CoordType = "separate" + CoordInterleaved CoordType = "interleaved" +) + +// String returns the string representation of the coordinate type +func (c CoordType) String() string { + return string(c) +} + +// GeometryMetadata contains metadata for GeoArrow geometry types +type GeometryMetadata struct { + // Encoding specifies the geometry encoding format + Encoding GeometryEncoding `json:"encoding,omitempty"` + + // CRS contains PROJJSON coordinate reference system information + CRS json.RawMessage `json:"crs,omitempty"` + + // Edges specifies the edge interpretation for the geometry + Edges EdgeType `json:"edges,omitempty"` + + // CoordType specifies the coordinate layout (separate vs interleaved) + CoordType CoordType `json:"coord_type,omitempty"` +} + +// NewGeometryMetadata creates a new GeometryMetadata with default values +func NewGeometryMetadata() *GeometryMetadata { + return &GeometryMetadata{ + Encoding: EncodingGeoArrow, + Edges: EdgePlanar, + CoordType: CoordSeparate, + } +} + +// Serialize serializes the metadata to a JSON string +func (gm *GeometryMetadata) Serialize() (string, error) { + if gm == nil { + return "", nil + } + data, err := json.Marshal(gm) + if err != nil { + return "", fmt.Errorf("failed to serialize geometry metadata: %w", err) + } + return string(data), nil +} + +// DeserializeGeometryMetadata deserializes geometry metadata from a JSON string +func DeserializeGeometryMetadata(data string) (*GeometryMetadata, error) { + if data == "" { + return NewGeometryMetadata(), nil + } + + var gm GeometryMetadata + if err := json.Unmarshal([]byte(data), &gm); err != nil { + return nil, fmt.Errorf("failed to deserialize geometry metadata: %w", err) + } + + return &gm, nil +} + +// createCoordinateType creates an Arrow data type for coordinates based on dimension +func createCoordinateType(dim CoordinateDimension) arrow.DataType { Review Comment: you need to have a switch on the coordinate type. While "separated" uses a struct with fields, "interleaved" is instead a `FixedSizeList<float64>[n_dim]` ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } Review Comment: I would do the json.Unmarshal in here instead of having the exported `DeserializeGeometryMetadata` ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} Review Comment: the comment says "NaN or zero" but you're only checking for NaN here ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { + numFields := structType.NumFields() + switch numFields { + case 2: + dim = DimensionXY + case 3: + // Check if it's XYZ or XYM by looking at field names + if structType.Field(2).Name == "z" { + dim = DimensionXYZ + } else { + dim = DimensionXYM + } + case 4: + dim = DimensionXYZM + default: + dim = DimensionXY + } + } + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: storageType}, + metadata: metadata, + dimension: dim, + }, nil +} + +// NewBuilder creates a new array builder for this type +func (p *PointType) NewBuilder(mem memory.Allocator) array.Builder { + return NewPointBuilder(mem, p) +} + +// Metadata returns the geometry metadata +func (p *PointType) Metadata() *GeometryMetadata { + return p.metadata +} + +// Dimension returns the coordinate dimension +func (p *PointType) Dimension() CoordinateDimension { + return p.dimension +} + +// PointArray represents an array of Point geometries +type PointArray struct { + array.ExtensionArrayBase +} + +// String returns a string representation of the array +func (p *PointArray) String() string { + o := new(strings.Builder) + o.WriteString("PointArray[") + for i := 0; i < p.Len(); i++ { + if i > 0 { + o.WriteString(" ") + } + if p.IsNull(i) { + o.WriteString(array.NullValueStr) + } else { + point := p.Value(i) + o.WriteString(point.String()) + } + } + o.WriteString("]") + return o.String() +} + +// Value returns the Point at the given index +func (p *PointArray) Value(i int) Point { + pointType := p.ExtensionType().(*PointType) + structArray := p.Storage().(*array.Struct) + + point := Point{Dimension: pointType.dimension} + + if p.IsNull(i) { + return point + } + + // Get X coordinate + xArray := structArray.Field(0).(*array.Float64) + point.X = xArray.Value(i) + + // Get Y coordinate + yArray := structArray.Field(1).(*array.Float64) + point.Y = yArray.Value(i) + + // Get Z coordinate if present + if pointType.dimension == DimensionXYZ || pointType.dimension == DimensionXYZM { + zArray := structArray.Field(2).(*array.Float64) + point.Z = zArray.Value(i) + } + + // Get M coordinate if present + switch pointType.dimension { + case DimensionXYM: + mArray := structArray.Field(2).(*array.Float64) + point.M = mArray.Value(i) + case DimensionXYZM: + mArray := structArray.Field(3).(*array.Float64) + point.M = mArray.Value(i) + } + + return point +} + +// Values returns all Point values as a slice +func (p *PointArray) Values() []Point { + values := make([]Point, p.Len()) + for i := range values { + values[i] = p.Value(i) + } + return values +} + +// ValueStr returns a string representation of the value at index i +func (p *PointArray) ValueStr(i int) string { + if p.IsNull(i) { + return array.NullValueStr + } + return p.Value(i).String() +} + +// GetOneForMarshal returns the value at index i for JSON marshaling +func (p *PointArray) GetOneForMarshal(i int) any { + if p.IsNull(i) { + return nil + } + point := p.Value(i) + switch point.Dimension { + case DimensionXY: + return []float64{point.X, point.Y} + case DimensionXYZ: + return []float64{point.X, point.Y, point.Z} + case DimensionXYM: + return []float64{point.X, point.Y, point.M} + case DimensionXYZM: + return []float64{point.X, point.Y, point.Z, point.M} + default: + // Should never happen but defensive programming + panic(fmt.Sprintf("unknown coordinate dimension: %v", point.Dimension)) + } +} + +// MarshalJSON implements json.Marshaler +func (p *PointArray) MarshalJSON() ([]byte, error) { + vals := make([]any, p.Len()) + for i := range vals { + vals[i] = p.GetOneForMarshal(i) + } + return json.Marshal(vals) +} + +// PointBuilder is an array builder for Point geometries +type PointBuilder struct { + *array.ExtensionBuilder +} + +// NewPointBuilder creates a new Point array builder +func NewPointBuilder(mem memory.Allocator, dtype *PointType) *PointBuilder { + return &PointBuilder{ + ExtensionBuilder: array.NewExtensionBuilder(mem, dtype), + } +} + +// Append appends a Point to the array +func (b *PointBuilder) Append(point Point) { + pointType := b.Type().(*PointType) + structBuilder := b.Builder.(*array.StructBuilder) + structBuilder.Append(true) Review Comment: same comment as above needs to handle the interleaved OR separate cases ########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) + +// String returns the string representation of the encoding +func (e GeometryEncoding) String() string { + switch e { + case EncodingGeoArrow: + return "geoarrow" + default: + return "unknown" + } +} + +// EdgeType represents the edge interpretation for geometry data +type EdgeType string + +const ( + EdgePlanar EdgeType = "planar" Review Comment: according to the docs, this isn't a valid value for the "edges" key, Planar is the type if the edges key is omitted entirely ########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) + +// String returns the string representation of the encoding +func (e GeometryEncoding) String() string { + switch e { + case EncodingGeoArrow: + return "geoarrow" + default: + return "unknown" + } +} + +// EdgeType represents the edge interpretation for geometry data +type EdgeType string + +const ( + EdgePlanar EdgeType = "planar" + EdgeSpherical EdgeType = "spherical" +) + +// String returns the string representation of the edge type +func (e EdgeType) String() string { + return string(e) +} + +// CoordType represents the coordinate layout type +type CoordType string + +const ( + CoordSeparate CoordType = "separate" + CoordInterleaved CoordType = "interleaved" +) + +// String returns the string representation of the coordinate type +func (c CoordType) String() string { + return string(c) +} + +// GeometryMetadata contains metadata for GeoArrow geometry types +type GeometryMetadata struct { + // Encoding specifies the geometry encoding format + Encoding GeometryEncoding `json:"encoding,omitempty"` Review Comment: I don't see this in the spec, where is this member coming from? ########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) + +// String returns the string representation of the encoding +func (e GeometryEncoding) String() string { + switch e { + case EncodingGeoArrow: + return "geoarrow" + default: + return "unknown" + } +} + +// EdgeType represents the edge interpretation for geometry data +type EdgeType string + +const ( + EdgePlanar EdgeType = "planar" + EdgeSpherical EdgeType = "spherical" +) Review Comment: missing the other edge types: "vincenty", "thomas", "andoyer" and "karney" ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) Review Comment: The logic here is incorrect. The storage type should always be compared and you're forgetting to compare the metadata ########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) + +// String returns the string representation of the encoding +func (e GeometryEncoding) String() string { + switch e { + case EncodingGeoArrow: + return "geoarrow" + default: + return "unknown" + } +} + +// EdgeType represents the edge interpretation for geometry data +type EdgeType string + +const ( + EdgePlanar EdgeType = "planar" + EdgeSpherical EdgeType = "spherical" +) + +// String returns the string representation of the edge type +func (e EdgeType) String() string { + return string(e) +} + +// CoordType represents the coordinate layout type +type CoordType string + +const ( + CoordSeparate CoordType = "separate" + CoordInterleaved CoordType = "interleaved" +) + +// String returns the string representation of the coordinate type +func (c CoordType) String() string { + return string(c) +} + +// GeometryMetadata contains metadata for GeoArrow geometry types +type GeometryMetadata struct { Review Comment: you're missing the optional `crs_type` member which should be one of * "projjson" * "wkt2:2019" * "authority_code" * "srid" ########## arrow/extensions/geoarrow.go: ########## @@ -0,0 +1,186 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + + "github.com/apache/arrow-go/v18/arrow" +) + +// CoordinateDimension represents the dimensionality of coordinates +type CoordinateDimension int + +const ( + DimensionXY CoordinateDimension = iota + DimensionXYZ + DimensionXYM + DimensionXYZM +) + +// String returns the string representation of the coordinate dimension +func (d CoordinateDimension) String() string { + switch d { + case DimensionXY: + return "xy" + case DimensionXYZ: + return "xyz" + case DimensionXYM: + return "xym" + case DimensionXYZM: + return "xyzm" + default: + return "unknown" + } +} + +// Size returns the number of coordinate values per point +func (d CoordinateDimension) Size() int { + switch d { + case DimensionXY: + return 2 + case DimensionXYZ, DimensionXYM: + return 3 + case DimensionXYZM: + return 4 + default: + return 2 + } +} + +// GeometryEncoding represents the encoding method for geometry data +type GeometryEncoding int + +const ( + EncodingGeoArrow GeometryEncoding = iota +) + +// String returns the string representation of the encoding +func (e GeometryEncoding) String() string { + switch e { + case EncodingGeoArrow: + return "geoarrow" + default: + return "unknown" + } +} + +// EdgeType represents the edge interpretation for geometry data +type EdgeType string + +const ( + EdgePlanar EdgeType = "planar" + EdgeSpherical EdgeType = "spherical" +) + +// String returns the string representation of the edge type +func (e EdgeType) String() string { + return string(e) +} + +// CoordType represents the coordinate layout type +type CoordType string + +const ( + CoordSeparate CoordType = "separate" + CoordInterleaved CoordType = "interleaved" +) + +// String returns the string representation of the coordinate type +func (c CoordType) String() string { + return string(c) +} + +// GeometryMetadata contains metadata for GeoArrow geometry types +type GeometryMetadata struct { + // Encoding specifies the geometry encoding format + Encoding GeometryEncoding `json:"encoding,omitempty"` + + // CRS contains PROJJSON coordinate reference system information + CRS json.RawMessage `json:"crs,omitempty"` Review Comment: While it should be PROJJSON it could also be other formats ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) Review Comment: move this to the bottom and have it be a `fallthrough` instead of repeating it ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { Review Comment: The comment for `IsEmpty` says that an empty point would have no dimension, so we shouldn't initialize it with `DimensionXY` ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized Review Comment: personally I'd have the json.Marshal and such in here rather than having an explicit `Serialize` method on the metadata object. ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { Review Comment: need to check for the interleaved version too ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } Review Comment: ```suggestion return NewPointTypeWithMetadata(dim, nil) ``` ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { + numFields := structType.NumFields() + switch numFields { + case 2: + dim = DimensionXY + case 3: + // Check if it's XYZ or XYM by looking at field names + if structType.Field(2).Name == "z" { + dim = DimensionXYZ + } else { + dim = DimensionXYM + } + case 4: + dim = DimensionXYZM + default: + dim = DimensionXY + } + } + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: storageType}, + metadata: metadata, + dimension: dim, + }, nil +} + +// NewBuilder creates a new array builder for this type +func (p *PointType) NewBuilder(mem memory.Allocator) array.Builder { + return NewPointBuilder(mem, p) +} + +// Metadata returns the geometry metadata +func (p *PointType) Metadata() *GeometryMetadata { + return p.metadata +} + +// Dimension returns the coordinate dimension +func (p *PointType) Dimension() CoordinateDimension { + return p.dimension +} + +// PointArray represents an array of Point geometries +type PointArray struct { + array.ExtensionArrayBase +} + +// String returns a string representation of the array +func (p *PointArray) String() string { + o := new(strings.Builder) + o.WriteString("PointArray[") Review Comment: don't need `PointArray` in front, we'll automatically add the type when printing ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { + numFields := structType.NumFields() + switch numFields { + case 2: + dim = DimensionXY + case 3: + // Check if it's XYZ or XYM by looking at field names + if structType.Field(2).Name == "z" { + dim = DimensionXYZ + } else { + dim = DimensionXYM + } + case 4: + dim = DimensionXYZM + default: + dim = DimensionXY + } + } + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: storageType}, + metadata: metadata, + dimension: dim, + }, nil +} + +// NewBuilder creates a new array builder for this type +func (p *PointType) NewBuilder(mem memory.Allocator) array.Builder { + return NewPointBuilder(mem, p) +} + +// Metadata returns the geometry metadata +func (p *PointType) Metadata() *GeometryMetadata { + return p.metadata +} + +// Dimension returns the coordinate dimension +func (p *PointType) Dimension() CoordinateDimension { + return p.dimension +} + +// PointArray represents an array of Point geometries +type PointArray struct { + array.ExtensionArrayBase +} + +// String returns a string representation of the array +func (p *PointArray) String() string { + o := new(strings.Builder) + o.WriteString("PointArray[") + for i := 0; i < p.Len(); i++ { + if i > 0 { + o.WriteString(" ") + } + if p.IsNull(i) { + o.WriteString(array.NullValueStr) + } else { + point := p.Value(i) + o.WriteString(point.String()) + } + } + o.WriteString("]") + return o.String() +} + +// Value returns the Point at the given index +func (p *PointArray) Value(i int) Point { + pointType := p.ExtensionType().(*PointType) + structArray := p.Storage().(*array.Struct) + + point := Point{Dimension: pointType.dimension} + + if p.IsNull(i) { + return point + } + + // Get X coordinate + xArray := structArray.Field(0).(*array.Float64) + point.X = xArray.Value(i) + + // Get Y coordinate + yArray := structArray.Field(1).(*array.Float64) + point.Y = yArray.Value(i) + + // Get Z coordinate if present + if pointType.dimension == DimensionXYZ || pointType.dimension == DimensionXYZM { + zArray := structArray.Field(2).(*array.Float64) + point.Z = zArray.Value(i) + } + + // Get M coordinate if present + switch pointType.dimension { + case DimensionXYM: + mArray := structArray.Field(2).(*array.Float64) + point.M = mArray.Value(i) + case DimensionXYZM: + mArray := structArray.Field(3).(*array.Float64) + point.M = mArray.Value(i) + } + + return point +} + +// Values returns all Point values as a slice +func (p *PointArray) Values() []Point { + values := make([]Point, p.Len()) + for i := range values { + values[i] = p.Value(i) + } + return values +} + +// ValueStr returns a string representation of the value at index i +func (p *PointArray) ValueStr(i int) string { + if p.IsNull(i) { + return array.NullValueStr + } + return p.Value(i).String() +} + +// GetOneForMarshal returns the value at index i for JSON marshaling +func (p *PointArray) GetOneForMarshal(i int) any { + if p.IsNull(i) { + return nil + } + point := p.Value(i) + switch point.Dimension { + case DimensionXY: + return []float64{point.X, point.Y} + case DimensionXYZ: + return []float64{point.X, point.Y, point.Z} + case DimensionXYM: + return []float64{point.X, point.Y, point.M} + case DimensionXYZM: + return []float64{point.X, point.Y, point.Z, point.M} + default: + // Should never happen but defensive programming + panic(fmt.Sprintf("unknown coordinate dimension: %v", point.Dimension)) + } Review Comment: implement `MarshalJSON` on the Point type and then just use that ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { + numFields := structType.NumFields() + switch numFields { + case 2: + dim = DimensionXY + case 3: + // Check if it's XYZ or XYM by looking at field names + if structType.Field(2).Name == "z" { + dim = DimensionXYZ + } else { + dim = DimensionXYM + } + case 4: + dim = DimensionXYZM + default: + dim = DimensionXY + } + } + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: storageType}, + metadata: metadata, + dimension: dim, + }, nil +} + +// NewBuilder creates a new array builder for this type +func (p *PointType) NewBuilder(mem memory.Allocator) array.Builder { + return NewPointBuilder(mem, p) +} + +// Metadata returns the geometry metadata +func (p *PointType) Metadata() *GeometryMetadata { + return p.metadata +} + +// Dimension returns the coordinate dimension +func (p *PointType) Dimension() CoordinateDimension { + return p.dimension +} + +// PointArray represents an array of Point geometries +type PointArray struct { + array.ExtensionArrayBase +} + +// String returns a string representation of the array +func (p *PointArray) String() string { + o := new(strings.Builder) + o.WriteString("PointArray[") + for i := 0; i < p.Len(); i++ { + if i > 0 { + o.WriteString(" ") + } + if p.IsNull(i) { + o.WriteString(array.NullValueStr) + } else { + point := p.Value(i) + o.WriteString(point.String()) + } + } + o.WriteString("]") + return o.String() +} + +// Value returns the Point at the given index +func (p *PointArray) Value(i int) Point { + pointType := p.ExtensionType().(*PointType) + structArray := p.Storage().(*array.Struct) + + point := Point{Dimension: pointType.dimension} + + if p.IsNull(i) { + return point Review Comment: why not use `NewEmptyPoint`? ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { + numFields := structType.NumFields() + switch numFields { + case 2: + dim = DimensionXY + case 3: + // Check if it's XYZ or XYM by looking at field names + if structType.Field(2).Name == "z" { + dim = DimensionXYZ + } else { + dim = DimensionXYM + } + case 4: + dim = DimensionXYZM + default: + dim = DimensionXY + } + } + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: storageType}, + metadata: metadata, + dimension: dim, + }, nil +} + +// NewBuilder creates a new array builder for this type +func (p *PointType) NewBuilder(mem memory.Allocator) array.Builder { + return NewPointBuilder(mem, p) +} + +// Metadata returns the geometry metadata +func (p *PointType) Metadata() *GeometryMetadata { + return p.metadata +} + +// Dimension returns the coordinate dimension +func (p *PointType) Dimension() CoordinateDimension { + return p.dimension +} + +// PointArray represents an array of Point geometries +type PointArray struct { + array.ExtensionArrayBase +} + +// String returns a string representation of the array +func (p *PointArray) String() string { + o := new(strings.Builder) + o.WriteString("PointArray[") + for i := 0; i < p.Len(); i++ { + if i > 0 { + o.WriteString(" ") + } + if p.IsNull(i) { + o.WriteString(array.NullValueStr) + } else { + point := p.Value(i) + o.WriteString(point.String()) + } + } + o.WriteString("]") + return o.String() +} + +// Value returns the Point at the given index +func (p *PointArray) Value(i int) Point { + pointType := p.ExtensionType().(*PointType) + structArray := p.Storage().(*array.Struct) Review Comment: need to determine whether it is interleaved or separate before you assume it's a struct ########## arrow/extensions/geoarrow_point.go: ########## @@ -0,0 +1,407 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package extensions + +import ( + "encoding/json" + "fmt" + "math" + "reflect" + "strings" + + "github.com/apache/arrow-go/v18/arrow" + "github.com/apache/arrow-go/v18/arrow/array" + "github.com/apache/arrow-go/v18/arrow/memory" +) + +// Point represents a point geometry with coordinates +type Point struct { + X, Y, Z, M float64 + Dimension CoordinateDimension +} + +// NewPoint creates a new 2D point +func NewPoint(x, y float64) Point { + return Point{X: x, Y: y, Dimension: DimensionXY} +} + +// NewPointZ creates a new 3D point with Z coordinate +func NewPointZ(x, y, z float64) Point { + return Point{X: x, Y: y, Z: z, Dimension: DimensionXYZ} +} + +// NewPointM creates a new 2D point with M coordinate +func NewPointM(x, y, m float64) Point { + return Point{X: x, Y: y, M: m, Dimension: DimensionXYM} +} + +// NewPointZM creates a new 3D point with Z and M coordinates +func NewPointZM(x, y, z, m float64) Point { + return Point{X: x, Y: y, Z: z, M: m, Dimension: DimensionXYZM} +} + +// NewEmptyPoint creates a new empty point +func NewEmptyPoint() Point { + return Point{X: math.NaN(), Y: math.NaN(), Z: math.NaN(), M: math.NaN(), Dimension: DimensionXY} +} + +// IsEmpty returns true if this is an empty point (all coordinates are NaN or zero with no dimension) +func (p Point) IsEmpty() bool { + return math.IsNaN(p.X) && math.IsNaN(p.Y) && math.IsNaN(p.Z) && math.IsNaN(p.M) +} + +// String returns a string representation of the point +func (p Point) String() string { + if p.IsEmpty() { + return "POINT EMPTY" + } + + switch p.Dimension { + case DimensionXY: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + case DimensionXYZ: + return fmt.Sprintf("POINT Z(%.6f %.6f %.6f)", p.X, p.Y, p.Z) + case DimensionXYM: + return fmt.Sprintf("POINT M(%.6f %.6f %.6f)", p.X, p.Y, p.M) + case DimensionXYZM: + return fmt.Sprintf("POINT ZM(%.6f %.6f %.6f %.6f)", p.X, p.Y, p.Z, p.M) + default: + return fmt.Sprintf("POINT(%.6f %.6f)", p.X, p.Y) + } +} + +// PointType is the extension type for Point geometries +type PointType struct { + arrow.ExtensionBase + metadata *GeometryMetadata + dimension CoordinateDimension +} + +// NewPointType creates a new Point extension type for 2D points +func NewPointType() *PointType { + return NewPointTypeWithDimension(DimensionXY) +} + +// NewPointTypeWithDimension creates a new Point extension type with specified dimension +func NewPointTypeWithDimension(dim CoordinateDimension) *PointType { + metadata := NewGeometryMetadata() + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// NewPointTypeWithMetadata creates a new Point extension type with custom metadata +func NewPointTypeWithMetadata(dim CoordinateDimension, metadata *GeometryMetadata) *PointType { + if metadata == nil { + metadata = NewGeometryMetadata() + } + coordType := createCoordinateType(dim) + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: coordType}, + metadata: metadata, + dimension: dim, + } +} + +// ArrayType returns the array type for Point arrays +func (*PointType) ArrayType() reflect.Type { + return reflect.TypeOf(PointArray{}) +} + +// ExtensionName returns the name of the extension +func (*PointType) ExtensionName() string { + return "geoarrow.point" +} + +// String returns a string representation of the type +func (p *PointType) String() string { + return fmt.Sprintf("extension<%s[%s]>", p.ExtensionName(), p.dimension.String()) +} + +// ExtensionEquals checks if two extension types are equal +func (p *PointType) ExtensionEquals(other arrow.ExtensionType) bool { + if p.ExtensionName() != other.ExtensionName() { + return false + } + if otherPoint, ok := other.(*PointType); ok { + return p.dimension == otherPoint.dimension + } + return arrow.TypeEqual(p.Storage, other.StorageType()) +} + +// Serialize serializes the extension type metadata +func (p *PointType) Serialize() string { + if p.metadata == nil { + return "" + } + serialized, _ := p.metadata.Serialize() + return serialized +} + +// Deserialize deserializes the extension type metadata +func (*PointType) Deserialize(storageType arrow.DataType, data string) (arrow.ExtensionType, error) { + metadata, err := DeserializeGeometryMetadata(data) + if err != nil { + return nil, err + } + + // Determine dimension from storage type + dim := DimensionXY + if structType, ok := storageType.(*arrow.StructType); ok { + numFields := structType.NumFields() + switch numFields { + case 2: + dim = DimensionXY + case 3: + // Check if it's XYZ or XYM by looking at field names + if structType.Field(2).Name == "z" { + dim = DimensionXYZ + } else { + dim = DimensionXYM + } + case 4: + dim = DimensionXYZM + default: + dim = DimensionXY + } + } + + return &PointType{ + ExtensionBase: arrow.ExtensionBase{Storage: storageType}, + metadata: metadata, + dimension: dim, + }, nil +} + +// NewBuilder creates a new array builder for this type +func (p *PointType) NewBuilder(mem memory.Allocator) array.Builder { + return NewPointBuilder(mem, p) +} + +// Metadata returns the geometry metadata +func (p *PointType) Metadata() *GeometryMetadata { + return p.metadata +} + +// Dimension returns the coordinate dimension +func (p *PointType) Dimension() CoordinateDimension { + return p.dimension +} + +// PointArray represents an array of Point geometries +type PointArray struct { + array.ExtensionArrayBase +} + +// String returns a string representation of the array +func (p *PointArray) String() string { + o := new(strings.Builder) + o.WriteString("PointArray[") + for i := 0; i < p.Len(); i++ { + if i > 0 { + o.WriteString(" ") + } + if p.IsNull(i) { + o.WriteString(array.NullValueStr) + } else { + point := p.Value(i) + o.WriteString(point.String()) + } + } + o.WriteString("]") + return o.String() +} + +// Value returns the Point at the given index +func (p *PointArray) Value(i int) Point { + pointType := p.ExtensionType().(*PointType) + structArray := p.Storage().(*array.Struct) + + point := Point{Dimension: pointType.dimension} + + if p.IsNull(i) { + return point + } + + // Get X coordinate + xArray := structArray.Field(0).(*array.Float64) + point.X = xArray.Value(i) + + // Get Y coordinate + yArray := structArray.Field(1).(*array.Float64) + point.Y = yArray.Value(i) + + // Get Z coordinate if present + if pointType.dimension == DimensionXYZ || pointType.dimension == DimensionXYZM { + zArray := structArray.Field(2).(*array.Float64) + point.Z = zArray.Value(i) + } + + // Get M coordinate if present + switch pointType.dimension { + case DimensionXYM: + mArray := structArray.Field(2).(*array.Float64) + point.M = mArray.Value(i) + case DimensionXYZM: + mArray := structArray.Field(3).(*array.Float64) + point.M = mArray.Value(i) + } + + return point +} + +// Values returns all Point values as a slice +func (p *PointArray) Values() []Point { + values := make([]Point, p.Len()) + for i := range values { + values[i] = p.Value(i) + } + return values +} + +// ValueStr returns a string representation of the value at index i +func (p *PointArray) ValueStr(i int) string { + if p.IsNull(i) { + return array.NullValueStr + } + return p.Value(i).String() +} + +// GetOneForMarshal returns the value at index i for JSON marshaling +func (p *PointArray) GetOneForMarshal(i int) any { + if p.IsNull(i) { + return nil + } + point := p.Value(i) + switch point.Dimension { + case DimensionXY: + return []float64{point.X, point.Y} + case DimensionXYZ: + return []float64{point.X, point.Y, point.Z} + case DimensionXYM: + return []float64{point.X, point.Y, point.M} + case DimensionXYZM: + return []float64{point.X, point.Y, point.Z, point.M} + default: + // Should never happen but defensive programming + panic(fmt.Sprintf("unknown coordinate dimension: %v", point.Dimension)) + } +} + +// MarshalJSON implements json.Marshaler +func (p *PointArray) MarshalJSON() ([]byte, error) { + vals := make([]any, p.Len()) + for i := range vals { + vals[i] = p.GetOneForMarshal(i) + } + return json.Marshal(vals) +} + +// PointBuilder is an array builder for Point geometries +type PointBuilder struct { + *array.ExtensionBuilder +} + +// NewPointBuilder creates a new Point array builder +func NewPointBuilder(mem memory.Allocator, dtype *PointType) *PointBuilder { + return &PointBuilder{ + ExtensionBuilder: array.NewExtensionBuilder(mem, dtype), + } +} + +// Append appends a Point to the array +func (b *PointBuilder) Append(point Point) { + pointType := b.Type().(*PointType) + structBuilder := b.Builder.(*array.StructBuilder) + structBuilder.Append(true) + + // X coordinate + xBuilder := structBuilder.FieldBuilder(0).(*array.Float64Builder) + xBuilder.Append(point.X) + + // Y coordinate + yBuilder := structBuilder.FieldBuilder(1).(*array.Float64Builder) + yBuilder.Append(point.Y) + + // Z coordinate if present + if pointType.dimension == DimensionXYZ || pointType.dimension == DimensionXYZM { + zBuilder := structBuilder.FieldBuilder(2).(*array.Float64Builder) + zBuilder.Append(point.Z) + } + + // M coordinate if present + switch pointType.dimension { + case DimensionXYM: + mBuilder := structBuilder.FieldBuilder(2).(*array.Float64Builder) + mBuilder.Append(point.M) + case DimensionXYZM: + mBuilder := structBuilder.FieldBuilder(3).(*array.Float64Builder) + mBuilder.Append(point.M) + } +} + +// AppendXY appends a 2D point to the array +func (b *PointBuilder) AppendXY(x, y float64) { + b.Append(NewPoint(x, y)) +} + +// AppendXYZ appends a 3D point to the array +func (b *PointBuilder) AppendXYZ(x, y, z float64) { + b.Append(NewPointZ(x, y, z)) +} + +// AppendXYM appends a 2D point with M coordinate to the array +func (b *PointBuilder) AppendXYM(x, y, m float64) { + b.Append(NewPointM(x, y, m)) +} + +// AppendXYZM appends a 3D point with M coordinate to the array +func (b *PointBuilder) AppendXYZM(x, y, z, m float64) { + b.Append(NewPointZM(x, y, z, m)) +} + +// AppendNull appends a null value to the array +func (b *PointBuilder) AppendNull() { + b.ExtensionBuilder.Builder.(*array.StructBuilder).AppendNull() Review Comment: same comment as above, need to handle the FixedSizeList case -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
