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]

Reply via email to