@testset "Basic types" begin
    point = Point(2, 3)
    @test geomtrait(point) isa PointTrait
    @test testgeometry(point)
    @test ncoord(point) == 2
    @test getcoord(point, 2) == 3
    @test GeoInterface.coordinates(point) == [2, 3]

    line = Line(Point(2, 3), Point(4, 5))
    @test geomtrait(line) isa LineTrait
    @test testgeometry(line)
    @test ngeom(line) == 2
    @test getgeom(line, 2) == Point(4, 5)
    @test GeoInterface.coordinates(line) == [[2, 3], [4, 5]]

    mp = MultiPoint([point, point])
    @test geomtrait(mp) isa MultiPointTrait
    @test testgeometry(mp)
    @test ngeom(mp) == 2
    @test getgeom(mp, 2) == point
    @test GeoInterface.coordinates(mp) == [[2, 3], [2, 3]]

    linestring = LineString(Point{2,Int}[(10, 10), (20, 20), (10, 40)])
    @test geomtrait(linestring) isa LineStringTrait
    @test testgeometry(linestring)
    @test ngeom(linestring) == 3
    @test ncoord(linestring) == 2
    @test getgeom(linestring, 1) == Point(10, 10)
    @test getgeom(linestring, 2) == Point(20, 20)
    @test getgeom(linestring, 3) == Point(10, 40)
    @test GeoInterface.coordinates(linestring) == [[10, 10], [20, 20], [10, 40]]

    multilinestring = MultiLineString([linestring, linestring])
    @test geomtrait(multilinestring) isa MultiLineStringTrait
    @test testgeometry(multilinestring)
    @test GeoInterface.coordinates(multilinestring) ==
          [[[10, 10], [20, 20], [10, 40]], [[10, 10], [20, 20], [10, 40]]]
    @test ncoord(multilinestring) == 2

    poly = Polygon(rand(Point{2,Float32}, 5), [rand(Point{2,Float32}, 5)])
    @test geomtrait(poly) isa PolygonTrait
    @test testgeometry(poly)
    @test length(GeoInterface.coordinates(poly)) == 2
    @test length(GeoInterface.coordinates(poly)[1]) == 5

    triangle = Triangle(point, point, point)
    @test geomtrait(triangle) isa PolygonTrait # ?? should it be a Triangle trait
    @test testgeometry(triangle)
    @test length(GeoInterface.coordinates(triangle)) == 1
    @test length(GeoInterface.coordinates(triangle)[1]) == 3

    polys = MultiPolygon([poly, poly])
    @test geomtrait(polys) isa MultiPolygonTrait
    @test testgeometry(polys)
    @test length(GeoInterface.coordinates(polys)) == 2
    @test length(GeoInterface.coordinates(polys)[1]) == 2
    @test length(GeoInterface.coordinates(polys)[1][1]) == 5
end

@testset "Mesh" begin
    mesh = triangle_mesh(Sphere(Point3f(0), 1))
    @test testgeometry(mesh)
end

@testset "Convert" begin
    # convert GeoJSON geometry types to GeometryBasics via the GeoInterface
    point_str = """{"type":"Point","coordinates":[30.1,10.1]}"""
    point_3d_str = """{"type":"Point","coordinates":[30.1,10.1,5.1]}"""
    linestring_str = """{"type":"LineString","coordinates":[[30.1,10.1],[10.1,30.1],[40.1,40.1]]}"""
    polygon_str = """{"type":"Polygon","coordinates":[[[30.1,10.1],[40.1,40.1],[20.1,40.1],[10.1,20.1],[30.1,10.1]]]}"""
    polygon_hole_str = """{"type":"Polygon","coordinates":[[[35.1,10.1],[45.1,45.1],[15.1,40.1],[10.1,20.1],[35.1,10.1]],[[20.1,30.1],[35.1,35.1],[30.1,20.1],[20.1,30.1]]]}"""
    multipoint_str = """{"type":"MultiPoint","coordinates":[[10.1,40.1],[40.1,30.1],[20.1,20.1],[30.1,10.1]]}"""
    multilinestring_str = """{"type":"MultiLineString","coordinates":[[[10.1,10.1],[20.1,20.1],[10.1,40.1]],[[40.1,40.1],[30.1,30.1],[40.1,20.1],[30.1,10.1]]]}"""
    multipolygon_str = """{"type":"MultiPolygon","coordinates":[[[[30.1,20.1],[45.1,40.1],[10.1,40.1],[30.1,20.1]]],[[[15.1,5.1],[40.1,10.1],[10.1,20.1],[5.1,10.1],[15.1,5.1]]]]}"""
    multipolygon_hole_str = """{"type":"MultiPolygon","coordinates":[[[[40.1,40.1],[20.1,45.1],[45.1,30.1],[40.1,40.1]]],[[[20.1,35.1],[10.1,30.1],[10.1,10.1],[30.1,5.1],[45.1,20.1],[20.1,35.1]],[[30.1,20.1],[20.1,15.1],[20.1,25.1],[30.1,20.1]]]]}"""

    point_json = GeoJSON.read(point_str)
    point_3d_json = GeoJSON.read(point_3d_str)
    linestring_json = GeoJSON.read(linestring_str)
    polygon_json = GeoJSON.read(polygon_str)
    polygon_hole_json = GeoJSON.read(polygon_hole_str)
    multipoint_json = GeoJSON.read(multipoint_str)
    multilinestring_json = GeoJSON.read(multilinestring_str)
    multipolygon_json = GeoJSON.read(multipolygon_str)
    multipolygon_hole_json = GeoJSON.read(multipolygon_hole_str)

    point_gb = GeoInterface.convert(GeometryBasics, point_json)
    point_3d_gb = GeoInterface.convert(GeometryBasics, point_3d_json)
    linestring_gb = GeoInterface.convert(GeometryBasics, linestring_json)
    polygon_gb = GeoInterface.convert(GeometryBasics, polygon_json)
    polygon_hole_gb = GeoInterface.convert(GeometryBasics, polygon_hole_json)
    multipoint_gb = GeoInterface.convert(GeometryBasics, multipoint_json)
    multilinestring_gb = GeoInterface.convert(GeometryBasics, multilinestring_json)
    multipolygon_gb = GeoInterface.convert(GeometryBasics, multipolygon_json)
    multipolygon_hole_gb = GeoInterface.convert(GeometryBasics, multipolygon_hole_json)
    
    @test point_gb === Point{2,Float32}(30.1, 10.1)
    @test point_3d_gb === Point{3,Float32}(30.1, 10.1, 5.1)
    @test linestring_gb isa LineString
    # TODO, what should we do exactly with linestrings?
    # @test length(linestring_gb) == 2
    # @test eltype(linestring_gb) == Line{2, Float64}

    @test polygon_gb isa Polygon
    @test isempty(polygon_gb.interiors)
    @test polygon_hole_gb isa Polygon
    @test length(polygon_hole_gb.interiors) == 1
    @test multipoint_gb isa MultiPoint
    @test length(multipoint_gb) == 4
    @test multipoint_gb[4] === Point{2,Float32}(30.1, 10.1)
    @test multilinestring_gb isa MultiLineString
    @test length(multilinestring_gb) == 2
    @test multipolygon_gb isa MultiPolygon
    @test length(multipolygon_gb) == 2
    @test multipolygon_hole_gb isa MultiPolygon
    @test length(multipolygon_hole_gb) == 2
    @test length(multipolygon_hole_gb[1].interiors) == 0
    @test length(multipolygon_hole_gb[2].interiors) == 1
end

@testset "Extent" begin
    rect = Rect2f(Vec2f(0), Vec2f(1.0))
    ext = extent(rect)
    @test ext.X == (0.0f0, 1.0f0)
    @test ext.Y == (0.0f0, 1.0f0)
    rect = Rect3f(Vec3f(0), Vec3f(1.0))
    ext = extent(rect)
    @test ext.X == (0.0f0, 1.0f0)
    @test ext.Y == (0.0f0, 1.0f0)
    @test ext.Z == (0.0f0, 1.0f0)
    rect = Rect4f(Vec{4,Float32}(0), Vec{4,Float32}(1.0))
    ext = extent(rect)
    @test ext.X == (0.0f0, 1.0f0)
    @test ext.Y == (0.0f0, 1.0f0)
    @test ext.Z == (0.0f0, 1.0f0)
    @test ext.M == (0.0f0, 1.0f0)
end

@testset "coordtype" begin
    if isdefined(GeoInterface, :coordtype)
        # Point
        point_int = Point(2, 3)
        point_float = Point(2.0, 3.0)
        point_float32 = Point{2,Float32}(2.0, 3.0)
        point_3d = Point{3,Float64}(1.0, 2.0, 3.0)
        @test GeoInterface.coordtype(point_int) == Int
        @test GeoInterface.coordtype(point_float) == Float64
        @test GeoInterface.coordtype(point_float32) == Float32
        @test GeoInterface.coordtype(point_3d) == Float64

        # Line (AbstractGeometry)
        line = Line(Point(2, 3), Point(4, 5))
        line_float = Line(Point(2.0, 3.0), Point(4.0, 5.0))
        @test GeoInterface.coordtype(line) == Int
        @test GeoInterface.coordtype(line_float) == Float64

        # LineString
        linestring = LineString(Point{2,Int}[(10, 10), (20, 20), (10, 40)])
        linestring_float = LineString(Point{2,Float64}[(10.0, 10.0), (20.0, 20.0)])
        @test GeoInterface.coordtype(linestring) == Int
        @test GeoInterface.coordtype(linestring_float) == Float64

        # MultiPoint
        mp = MultiPoint([Point(2, 3), Point(4, 5)])
        mp_float = MultiPoint([Point(2.0, 3.0), Point(4.0, 5.0)])
        @test GeoInterface.coordtype(mp) == Int
        @test GeoInterface.coordtype(mp_float) == Float64

        # MultiLineString
        mls = MultiLineString([linestring, linestring])
        mls_float = MultiLineString([linestring_float, linestring_float])
        @test GeoInterface.coordtype(mls) == Int
        @test GeoInterface.coordtype(mls_float) == Float64

        # Polygon
        poly_float32 = Polygon(rand(Point{2,Float32}, 5))
        poly_float64 = Polygon(rand(Point{2,Float64}, 5))
        @test GeoInterface.coordtype(poly_float32) == Float32
        @test GeoInterface.coordtype(poly_float64) == Float64

        # MultiPolygon
        mpoly = MultiPolygon([poly_float32, poly_float32])
        @test GeoInterface.coordtype(mpoly) == Float32

        # Simplex (Triangle is Ngon which is a Simplex)
        triangle = Triangle(Point(1, 2), Point(3, 4), Point(5, 6))
        triangle_float = Triangle(Point(1.0, 2.0), Point(3.0, 4.0), Point(5.0, 6.0))
        @test GeoInterface.coordtype(triangle) == Int
        @test GeoInterface.coordtype(triangle_float) == Float64

        # Mesh
        mesh = triangle_mesh(Sphere(Point3f(0), 1))
        @test GeoInterface.coordtype(mesh) == Float32
    end
end

@testset "Convert from StaticArrays" begin
    using GeometryBasics.StaticArrays

    # LineString from StaticVector
    ls = GeoInterface.convert(GeometryBasics, GeoInterface.LineString(SA[(1.0, 2.0), (3.0, 4.0)]))
    @test ls isa LineString{2, Float64}
    @test length(ls) == 2
    @test ls.points[1] == Point(1.0, 2.0)
    @test ls.points[2] == Point(3.0, 4.0)

    # Polygon from StaticVector of StaticVector rings
    ring = GeoInterface.LinearRing(SA[(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 1.0), (0.0, 0.0)])
    poly = GeoInterface.convert(GeometryBasics, GeoInterface.Polygon(SA[ring]))
    @test poly isa Polygon{2, Float64}

    # MultiLineString from StaticVector
    line1 = GeoInterface.LineString(SA[(1.0, 2.0), (3.0, 4.0)])
    line2 = GeoInterface.LineString(SA[(5.0, 6.0), (7.0, 8.0)])
    mls = GeoInterface.convert(GeometryBasics, GeoInterface.MultiLineString([line1, line2]))
    @test mls isa MultiLineString{2, Float64}
    @test length(mls) == 2
end

@testset "Convert Line and LinearRing" begin
    # Line (2D)
    gi_line_2d = GeoInterface.Line([(1.0, 2.0), (3.0, 4.0)])
    line_2d = GeoInterface.convert(GeometryBasics, gi_line_2d)
    @test line_2d isa Line{2, Float64}
    @test line_2d[1] == Point(1.0, 2.0)
    @test line_2d[2] == Point(3.0, 4.0)

    # Line (3D)
    gi_line_3d = GeoInterface.Line([(1.0, 2.0, 3.0), (4.0, 5.0, 6.0)])
    line_3d = GeoInterface.convert(GeometryBasics, gi_line_3d)
    @test line_3d isa Line{3, Float64}
    @test line_3d[1] == Point(1.0, 2.0, 3.0)
    @test line_3d[2] == Point(4.0, 5.0, 6.0)

    # LinearRing → LineString (2D)
    gi_ring = GeoInterface.LinearRing([(0.0, 0.0), (1.0, 0.0), (1.0, 1.0), (0.0, 0.0)])
    ls_from_ring = GeoInterface.convert(GeometryBasics, gi_ring)
    @test ls_from_ring isa LineString{2, Float64}
    @test length(ls_from_ring) == 4

    # LinearRing → LineString (3D)
    gi_ring_3d = GeoInterface.LinearRing([(0.0, 0.0, 0.0), (1.0, 0.0, 0.0), (1.0, 1.0, 0.0), (0.0, 0.0, 0.0)])
    ls_from_ring_3d = GeoInterface.convert(GeometryBasics, gi_ring_3d)
    @test ls_from_ring_3d isa LineString{3, Float64}
    @test length(ls_from_ring_3d) == 4
end
