GithubHelp home page GithubHelp logo

geometrybasics.jl's People

Contributors

alhirzel avatar ambyld avatar asinghvi17 avatar daschw avatar evetion avatar ffreyer avatar github-actions[bot] avatar hyrodium avatar juliohm avatar jw3126 avatar knuesel avatar kylejbrown17 avatar lilithhafner avatar mforets avatar mohamed82008 avatar mtsch avatar oliverevans96 avatar pauljurczak avatar piever avatar putianyi889 avatar qiyang-ustc avatar rafaqz avatar ranocha avatar simondanisch avatar sjkelly avatar sov-trotter avatar timholy avatar tokazama avatar visr avatar yakir12 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

geometrybasics.jl's Issues

Static analysis of a Type

In GeometryTypes there were a series of functions (vertextype, facetype) useful in extending calls to AbstractMesh.

These were pretty useful for avoiding calls to convert. Is similar functionality possible in GeometryBasics?

heterogeneous feature collection

I understand that the way to have geometries with attributes behave like tables in this package is to create a StructArray for the collection.

Do you have any idea what we could do when we want to represent different geometry types? Most commonly a dataset is all of the same type, but there are exceptions, and it would be nice to be able to represent them as well.

This understandably fails:

point = meta(Point(3, 1), city="Abuja", rainfall=1221.2)
polygon = meta(Polygon(Point{2, Int}[(3, 1), (4, 4), (2, 4), (1, 2), (3, 1)]), city="Borongan", rainfall=4114.8)

StructArray([point, polygon])  # ArgumentError: type does not have a definite number of fields

If I try to throw it into for instance a TypedTables.Table, it works fine, accepting geometry as a Vector{Any}.

using TypedTables
Table(
    geometry=[Point(3, 1), Polygon(Point{2, Int}[(3, 1), (4, 4), (2, 4), (1, 2), (3, 1)])],
    city=["Abuja", "Borongan"],
    rainfall=[1221.2, 4114.0],
)

I know this is more of a basic StructArrays vs TypedTables question, and understand that with a Vector{Any} things will be slower. But it would be nice to have a "GeometryBasics" table solution for this as well.

EDIT: concrete example here: https://github.com/visr/GeoJSONTables.jl/blob/4104e66a638814d77ef98af1d205450549361519/test/basics.jl#L97-L101

Polygon with BigFloats

The construction of a polygon with Float64 points such as

hexagon = Polygon(
    [Point(cos(θ), sin(θ)) for θ in 0 : π / 3 : 2π - π / 3]
)

does not throw an error, while the one with BigFloat points such as

hexagon = Polygon(
    [Point(BigFloat(cos(θ)), BigFloat(sin(θ))) for θ in 0 : π / 3 : 2π - π / 3]
)

does throw an error. The error thrown is

ERROR: ArgumentError: cannot reinterpret `Tuple{Point2{BigFloat}, Point2{BigFloat}}` as `Line{2, BigFloat}`, type `Line{2, BigFloat}` is not a bits type
Stacktrace:
 [1] (::Base.var"#throwbits#243")(S::Type, T::Type, U::Type)
   @ Base ./reinterpretarray.jl:16
 [2] reinterpret(#unused#::Type{Line{2, BigFloat}}, a::TupleView{Tuple{Point2{BigFloat}, Point2{BigFloat}}, 2, 1, Vector{Point2{BigFloat}}})
   @ Base ./reinterpretarray.jl:36
 [3] connect
   @ ~/.julia/packages/GeometryBasics/l4gkj/src/viewtypes.jl:77 [inlined]
 [4] LineString(points::Vector{Point2{BigFloat}}, skip::Int64)
   @ GeometryBasics ~/.julia/packages/GeometryBasics/l4gkj/src/basic_types.jl:209
 [5] Polygon(exterior::Vector{Point2{BigFloat}}, skip::Int64)
   @ GeometryBasics ~/.julia/packages/GeometryBasics/l4gkj/src/basic_types.jl:275
 [6] top-level scope
   @ REPL[559]:1

Is there a possible workaround for this error?

GB.meta() errors for MultiLineString( )

julia> GB.meta(GB.MultiLineString([GB.LineString([Point(0.0), Point(1.0)], [1, 2])]), boundingbox = Shapefile.Rect(0.0, 0.0, 100.0, 100.0))
ERROR: Metadata needs to be an array with the same length as data items. Found: Shapefile.Rect

One workaround is passing the metadata as boundingbox = [Rect(0.0, 0.0, 100.0, 100.0)] but I wonder if it's correct.
But this approach doesn't work for more than one items in metadata.

julia> GB.meta(GB.MultiLineString([GB.LineString([Point(0.0), Point(1.0)], [1, 2])]); m=[0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0], boundingbox = Rect(0.0, 0.0, 100.0, 100.0))
ERROR: Metadata array needs to have same length as data. Found 1 data items, and 15 metadata items
julia> a = GB.meta(GB.MultiLineString([GB.LineString([Point(0.0), Point(1.0)], [1, 2])]); m=[[0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0, 18.0, 20.0, 22.0, 24.0, 26.0, 28.0]], boundingbox = [[Rect(0.0, 0.0, 100.0, 100.0)]])
julia> meta(a)
ERROR: type NamedTuple has no field meta

Primitive to Signed Distance Field

I need the capability to compute the signed distance function to a primitive. (See https://discourse.julialang.org/t/which-package-function-for-signed-distance-fields/43487)

Since any(?) other primitive can can meshed, the first order of business would be a mesh2SDF function. I've found similar functions coded up in other languages (see post above). I would be happy to add this capability myself, but I'm pretty new to Julia (<1 month) and unlikely to get it in the package in a way that works for everyone.

Let me know if people are interested in this and willing to refactor some hacked together code.

Should `coordinate(::Point)` throw an error

I narrowed down an error I had to this

julia> coordinates(Point(2,2))
ERROR: Exception handling log message: UndefVarError(:Element)
  module=Base  file=errorshow.jl  line=302
  Second exception: UndefVarError(:Element)
MethodError: no method matching coordinates(::Point{2,Int64})
Stacktrace:
 [1] top-level scope at REPL[13]:1

Should coordinate(p::Point) throw an error or return p?

I also came across this while tracking it down

julia> methods(coordinates)
# 20 methods for generic function "coordinates":
[1] coordinates(x::LineString) in GeometryBasics at /Users/zchristensen/.julia/packages/GeometryBasics/x7LIA/src/basic_types.jl:196
[2] coordinates(mesh::FaceView) in GeometryBasics at /Users/zchristensen/.julia/packages/GeometryBasics/x7LIA/src/viewtypes.jl:159
[3] Error showing value of type Base.MethodList:
ERROR: UndefVarError: Element not defined
Stacktrace:
 [1] show(::IOContext{Base.GenericIOBuffer{Array{UInt8,1}}}, ::Type{
SYSTEM (REPL): showing an error caused an error
ERROR: UndefVarError: Element not defined
Stacktrace:
 [1] show(::IOContext{REPL.Terminals.TTYTerminal}, ::Type{
SYSTEM (REPL): caught exception of type UndefVarError while trying to handle a nested exception; giving up

Polygon: closed and right hand rule?

The Polygon type currently holds an exterior ring and one or more interior rings, but otherwise doesn't enforce much.

Currently it is not enforced that the first and last positions should be equal, or the right hand rule as in https://tools.ietf.org/html/rfc7946#section-3.1.6

Should we enforce these in this package? Or is it better to deal with those in packages such as GeoJSON.jl do you think?

TagBot trigger issue

This issue is used to trigger TagBot; feel free to unsubscribe.

If you haven't already, you should update your TagBot.yml to include issue comment triggers.
Please see this post on Discourse for instructions and more details.

If you'd like for me to do this for you, comment TagBot fix on this issue.
I'll open a PR within a few hours, please be patient!

Better show methods for triangle faces

Currently, the offset integers are shown using octal notation. Could we convert to decimal instead?

 TriangleFace(OffsetInteger{-1,UInt32}(0x0000003c), OffsetInteger{-1,UInt32}(0x0000003b), OffsetInteger{-1,UInt32}(0x0000003a))

could be be

 TriangleFace(OffsetInteger{-1,UInt32}(60), OffsetInteger{-1,UInt32}(59), OffsetInteger{-1,UInt32}(58))

or something.

`Base.in(point, set)` should error for if point type has different dimensionality

julia> in(GeometryBasics.Point(0,0), GeometryBasics.HyperRectangle(Vec(0,0,0), Vec(1,1,1)))
ERROR: BoundsError: attempt to access (0, 0)
  at index [3]
Stacktrace:
 [1] getindex at ./tuple.jl:24 [inlined]
 [2] getindex at /Users/goretkin/.julia/packages/GeometryBasics/csguK/src/fixed_arrays.jl:90 [inlined]
 [3] in(::Point{2,Int64}, ::GeometryBasics.HyperRectangle{3,Int64}) at /Users/goretkin/.julia/packages/GeometryBasics/csguK/src/primitives/rectangles.jl:491
 [4] top-level scope at REPL[64]:1

julia> in(GeometryBasics.Point(3,3), GeometryBasics.HyperRectangle(Vec(0,0,0), Vec(1,1,1)))
false

normals(mesh(Sphere)) generates NaN normal

Example:

m = mesh(Sphere(Point3f0(0), 1f0), nvertices=5)
normals(m)

Results in:

25-element Array{Vec{3,Float32},1}:
 [0.35740674, 0.35740674, 0.86285615]   
                                       
 [-0.35740674, -0.35740674, -0.86285615]
 [NaN, NaN, NaN]                        
 [0.57735026, -0.57735026, 0.57735026]  
 [0.7050465, -0.7050465, 0.076281935]   
 [0.6324556, -0.6324555, -0.4472136]    
 [0.35740674, -0.35740674, -0.86285615] 

This is independent of nvertices and the parameters given to Sphere as far as I can tell. I think the function that's failing is

function normals(vertices::AbstractVector{<: AbstractPoint{3, T}},

This can be avoided by passing a normaltype to mesh (which causes it to call decompose).

ensure consistency among `Base.iterate` and `Base.in`

The definitions at

@propagate_inbounds Base.getindex(x::Polytope, i::Integer) = coordinates(x)[i]
@propagate_inbounds Base.iterate(x::Polytope) = iterate(coordinates(x))
@propagate_inbounds Base.iterate(x::Polytope, i) = iterate(coordinates(x), i)

are convenient, but they induce a non-geometric definition of Base.in, due to the fallback definition https://github.com/JuliaLang/julia/blob/4b739e711a7241f48ec0525e97c44cc870a9af99/base/operators.jl#L1109-L1120 and furthermore a geometric definition like the following

https://github.com/JuliaGeometry/GeometryTypes.jl/blob/4f1ba71d7df1341087f84974f869ea7a421eafa8/src/polygon.jl#L21-L28

are inconsistent with the documentation for Base.in. Since it is a shame to be inconsistent (because generic programs may break since they may rely on a consistency of iterate and in, and since it is a shame to lose the notation of point ∈ polygon, I propose ensuring that either

  1. a type represents an infinite set of points and does not implement iterate
  2. a type represents a finite set of points and implements iterate

Redesigning Metadata processing and creating of meta-geometries

The current metadata handling features have been really interesting and there has been interest to expand it's functionality #50 #49.
The above mentioned issues have been made keeping in mind the heterogeneous nature of geometry/properties data. The present methods don't support this fully.
If I am correct, the way of creating meta-geometries is putting geometries and properties together into a StructArray, which works well for single geometries/properties or homogeneous geometries. The problem arises when we need a heterogeneous geometry with heterogeneous type like #49.

Now this functionality has been tested and implemented here we wish to extend it to GeometryBasics so that other use cases too can benefit from this API.

Make `Point` a subtype of `AbstractGeometry` (instead of `StaticVector`)

The type Point instantiates StaticVector, hence is closed under addition. However, the difference of two (affine) points is not a point, but a vector. This becomes relevant e.g. when computing (signed) volumes, say

det(a::Point{2}, b::Point{2}, c::Point{2}) = det(b-a, c-a)
det(a::Vec{2}, b::Vec{2}) = a[1]*b[2]-a[2]*b[1]

So of course, this is easy enough to fix in my own code, simply by defining

Base.:-(p::Point, q::Point) = Vec(p.data .- q.data)

However, that simple fix is still piracy, and it would make more sense to fix this on the GeometryBasics side. Moreover, since a point is a geometric object, in my opinion a more correct subtyping would be

abstract type AbstractPoint{Dim,T} <: AbstractGeometry{Dim,T} end

This would also make it easier to write methods that apply to AbstractGeometry objects, naturally including points (e.g. affine transformations).

clashing meta fields

I'd like to be able to represent user data with arbitrary field names.

Right now the meta_type macro allows you to specify the designated field name of the geometry, which is position for Point, polygon for Polygon, etc.

Now if I have some user data with the position field present:

using GeometryBasics
p = meta(Point(3, 1), city="Abuja", rainfall=1221.2, position=1)

Users may be surprised that they get the geometry back when they do p.position, and not 1.
One way to work around this is do do explicitly either meta(p).position or metafree(p).

This bugs me a little, so I wanted to bring this up to see if there are alternatives.

Should we leave out getproperty overloading for the geometry altogether? Or does that not work well together with the tables interface?

If we do use getproperty overloading, perhaps it is best to always use the same name, such as geometry, geom, coordinates.

Some geospatial formats that I'd like to represent here also allow naming your geometry field. What can we do in that case?

Probably using a function rather than a symbol is more flexibly here, similarly how R's sf has the st_geometry function.

meta() doesn't work for Polygon

Using the meta() function to add metadata to a polygon doesn't work.

julia> poly=Polygon(Point{2, Int}[(1, 1)])
Polygon{2,Int64,Point{2,Int64},LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},Array{LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},1}}(GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}}[], LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}}[])
julia> meta(poly, boundingbox=Rect(0,0,2,2))
ERROR: MethodError: no method matching Polygon(::Polygon{2,Int64,Point{2,Int64},LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},Array{LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},1}})`

while the PolygonMeta() works

julia> polym=PolygonMeta(Point{2, Int}[(3, 1)], boundingbox = Rect(0, 0, 2, 2))
PolygonMeta{2,Int64,Polygon{2,Int64,Point{2,Int64},LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},Array{LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},1}},(:boundingbox,),Tuple{GeometryBasics.HyperRectangle{2,Int64}}}(Polygon{2,Int64,Point{2,Int64},LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},Array{LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}},1}}(GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}}[], LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}}, 1}}}[]), (boundingbox = GeometryBasics.HyperRectangle{2,Int64}([0, 0], [2, 2]),))

Supertype of all geometries

What is a possible supertype of all geometries in GeometryBasics. I mean for a function where we are expecting GeometryBasics geometries(I want to multiple dispatch that function for GB geometries and some other structs too).
Also,

julia> using GeometryBasics

julia> Point isa UnionAll
true

julia> Point(1, 2) isa UnionAll
false

merging an empty vector of meshes / creating an empty mesh

This issue MakieOrg/Makie.jl#859 happens when an empty vector of meshes is merged. Currently, this errors, but I think it should create an empty Mesh. I don't know if that's currently possible, but I guess it should be, as these edge cases with zero objects happen all the time in dynamic plotting. It should be possible to draw an empty mesh

FaceView docstring example does not work

julia> using GeometryBasics

julia> FaceView(rand(Point3f0, 10), TriangleFace[(1, 2, 3), (2, 4, 5)])
ERROR: MethodError: no method matching FaceView(::Array{Point{3,Float32},1}, ::Array{NgonFace{3,T} where T,1})

Is this an error in the docstring or a bug? Also whats the recommended way to construct this view?

`?connect` example has typos, also, it does not work

help?> connect
search: connect continue countlines count_ones

  connect(points::AbstractVector{<: AbstractPoint}, P::Type{<: Polytype{N}}, skip::Int = N)

  Creates a view that connects a number of points to a Polytope P. Between each polytope, skip elements are skipped
  untill the next starts. Example: ```julia x = connect(Point[(1, 2), (3, 4), (5, 6), (7, 8)], Line, 2) x ==
  [Line(Point(1, 2), Point(3, 4)), Line(Point(5, 6), Point(7, 8))]

julia> x = connect(Point[(1, 2), (3, 4), (5, 6), (7, 8)], Line, 2)
ERROR: MethodError: no method matching Polytope(::Type{Line{Dim, T} where T where Dim}, ::Type{Point})
Closest candidates are:
  Polytope(::Type{var"#s10"} where var"#s10"<:(GeometryBasics.Ngon{Dim, T, N, P} where P where T where Dim), ::Type{var"#s9"} where var"#s9"<:AbstractPoint{NDim, T}) where {N, NDim, T} at C:\Users\jas\.julia\packages\GeometryBasics\csguK\src\basic_types.jl:89
Stacktrace:
 [1] connect(points::Vector{Point}, P::Type{Line{Dim, T} where T where Dim}, skip::Int64)
   @ GeometryBasics ~\.julia\packages\GeometryBasics\csguK\src\viewtypes.jl:77
 [2] top-level scope
   @ REPL[13]:1

coordinates of a Polygon

I have a pretty simple question but I can't seem to find the answer. I'll walk through my attempts, and have some more general questions about coordinates.

Given pol, how do I get a vector like pts back? I only need the exterior.

pts = Point{2, Int}[(3, 1), (4, 4), (2, 4), (1, 2), (3, 1)]
pol = Polygon(pts)

My first guess was coordinates

julia> coordinates(pol.exterior)
4-element reinterpret(GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}}, ::TupleView{Tuple{Point{2,Int64},Point{2,Int64}},2,1,Array{Point{2,Int64},1}}):
 Line([3, 1] => [4, 4])
 Line([4, 4] => [2, 4])
 Line([2, 4] => [1, 2])
 Line([1, 2] => [3, 1])

Somewhat surprisingly to me coordinates returns lines, not points. Of course I can dig deeper and get the coordinates of those:

julia> coordinates.(coordinates(pol.exterior))
4-element Array{StaticArrays.SArray{Tuple{2},Point{2,Int64},1,2},1}:
 [[3, 1], [4, 4]]
 [[4, 4], [2, 4]]
 [[2, 4], [1, 2]]
 [[1, 2], [3, 1]]

But now every inner point appears twice, which is not what I want. I think the TupleView is giving a lines view over the points that I want, but how do I get them? The coordinates documentation says to try decompose:

coordinates(geometry)

Returns the edges/vertices/coordinates of a geometry. Is allowed to return lazy iterators!
Use decompose(ConcretePointType, geometry) to get Vector{ConcretePointType} with
ConcretePointType to be something like Point{3, Float32}.

But that errors, is this a bug or am I using it wrongly?

julia> decompose(Point{2, Int}, pol.exterior)
ERROR: MethodError: no method matching Int64(::Point{2,Int64})
Closest candidates are:
  Int64(::Union{Bool, Int32, Int64, UInt32, UInt64, UInt8, Int128, Int16, Int8, UInt128, UInt16}) at boot.jl:708
  Int64(::Ptr) at boot.jl:718
  Int64(::Float32) at float.jl:706
  ...
Stacktrace:
 [1] (::GeometryBasics.var"#152#153"{Int64,GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},Int64})(::Int64) at C:\Users\visser_mn\.julia\dev\GeometryBasics\src\geometry_primitives.jl:57
 [2] ntuple at .\ntuple.jl:18 [inlined]
 [3] convert_simplex at C:\Users\visser_mn\.julia\dev\GeometryBasics\src\geometry_primitives.jl:57 [inlined]
 [4] collect_with_eltype(::Type{Point{2,Int64}}, ::Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}},2,1,Array{Point{2,Int64},1}}}) at C:\Users\visser_mn\.julia\dev\GeometryBasics\src\geometry_primitives.jl:76
 [5] decompose(::Type{Point{2,Int64}}, ::LineString{2,Int64,Point{2,Int64},Base.ReinterpretArray{GeometryBasics.Ngon{2,Int64,2,Point{2,Int64}},1,Tuple{Point{2,Int64},Point{2,Int64}},TupleView{Tuple{Point{2,Int64},Point{2,Int64}},2,1,Array{Point{2,Int64},1}}}}) at C:\Users\visser_mn\.julia\dev\GeometryBasics\src\interfaces.jl:104
 [6] top-level scope at REPL[21]:1

Maybe a little aside, but what is the main benefit of having coordinates return a line or point depending on the type? The field names in the code are a bit confusing as well, since coordinates(x::LineString) = x.points, but x.points are lines. I can see linestrings as either a collection of points or a vector of lines, so perhaps it would be better to have distinct functions for those? Because this seems a bit inconsistent at times, as we saw above coordinates(::Polygon) returns lines, but if we put in an Ngon, a fixed size polygon, then we get the points:

julia> tri = Triangle{2, Float64}([(3, 1), (4, 4), (2, 4)])
Triangle([3.0, 1.0], [4.0, 4.0], [2.0, 4.0])

julia> coordinates(tri)
3-element StaticArrays.SArray{Tuple{3},Point{2,Float64},1,3} with indices SOneTo(3):
 [3.0, 1.0]
 [4.0, 4.0]
 [2.0, 4.0]

heterogeneous geometry collection

Sorry for spamming the issues, but just want to get some of them out of my head :)

This issue is somewhat similar to #49, but that is about features (geometries + properties) with heterogeneous geometry types in a feature collection.

Some formats, like GeoJSON for instance, also support heterogeneous geometry collections, which are multigeometries like the multipoint or multipolygon we already have, but can consist of different geometry types. In GeoJSON it is called GeometryCollection. I'm wondering how we could represent them. It may be nice to have a geometry type like this in or based on this package, even if it will be hard to optimize.

EDIT: for now I just went with a bare Vector, though it may be nice to have that vector in a GeometryCollection type:
https://github.com/visr/GeoJSONTables.jl/blob/4104e66a638814d77ef98af1d205450549361519/test/basics.jl#L74-L76

Pb from GeometryTypes to GeometryBasics.jl?

Calling wireframe! with a Normal mesh I obtain the (new) error

MethodError: no method matching coordinates(::GLNormalMesh)
Stacktrace:
 [1] decompose(::Type{GeometryBasics.Point{3,Float32}}, ::GLNormalMesh) at $HOME/.julia/packages/GeometryBasics/ZOASp/src/geometry_primitives.jl:98
[2] (::AbstractPlotting.var"#351#352")(::GLNormalMesh) at $HOME.julia/packages/AbstractPlotting/7mERO/src/basic_recipes/basic_recipes.jl:280

Vec, Point, SVector, ... in the codebase

As I continue to study the source code to suggest improvements, I would like to clean up all the variations around the notion of a point as a static n-dimensional vector. Currently, the code uses Vec, Point, SVector in different places, and it is hard to understand if there is an actual reason behind these variations or if it is just a result of the code evolving quickly. Can we pick a single name and stick to it? I can submit a PR with a single name if that is ok.

Bounding box API

First let me say that the new documentation is really helpful. I am adding GeometryBasics.jl as a dependency in GeoStats.jl soon given that things are getting in place nicely here.

I've noticed that the boundingboxes.jl file contains axis-aligned bounding box algorithms for the different primitives and geometries. May I submit a PR replacing the Rect constructors by an actual boundbox function to be exported by the package? This way we separate the algorithm of bounding box from the actual constructor of the rectangle primitive. In the future, we could have alternative algorithms to the boundbox like for example convex and concave hulls convhull and conchull.

Docs reference `isdecomposable` which no longer exists

The decomposition docs at https://juliageometry.github.io/GeometryBasics.jl/dev/decomposition/ still mention isdecomposable:

# Let's take SimpleRectangle as an example:
# Below is a minimal set of decomposable attributes to build up a triangle mesh:
isdecomposable(::Type{T}, ::Type{HR}) where {T<:Point, HR<:SimpleRectangle} = true
isdecomposable(::Type{T}, ::Type{HR}) where {T<:Face, HR<:SimpleRectangle} = true

but that function doesn't exist in GeometryBasics. I'm guessing this left over from the GeometryTypes API.

Can we just implement decompose now instead of decompose and isdecomposable?

Deploy docs

We have docs but they aren't deployed. @SimonDanisch if you could add a Github secret token to the repo (see https://juliadocs.github.io/Documenter.jl/stable/man/hosting/#Authentication:-SSH-Deploy-Keys-1, https://bramtayl.github.io/OnlinePackage.jl/latest/), I can make a PR for doc deployment through Github Actions?

FWIW it's pretty easy to do this through OnlinePackage.jl. The workflow is:

using OnlinePackage
u = OnlinePackage.User("JuliaGeometry", readchomp("path/to/github/token"), "/usr/bin/ssh-keygen")
put_online(u, "GeometryBasics.jl")

Any intention to support different underlying data structure for meshes?

I am looking for a data structure where one can perform dynamic local mesh operations. (Specifically, I'm interested in 2d surface meshes, so examples of local mesh operations include edge flipping, edge collapsing, edge splitting, etc.) Meanwhile, I would also like to implement an efficient algorithm on mesh neighboring element traversal (for example, to find all triangles around a given vertex). Currently, I find that the surface meshes provided by this package keep track of a list of triangles (I might be missing something here), but then it would be very expensive to do neighbor element traversal or local mesh modifications. On the other hand, a half-edge based surface mesh (used by CGAL, for example) can be much more efficient in local mesh operations (which, of course, does not come without a price: it would use more memory space, and will use more indirections to find vertices of a face).

So my questions are:
Assuming various underlying mesh data structures representing the same mesh can be implemented, do they conceptually belong to this package? Or should they be implemented as a different package?
If it does conceptually belong to this package, would there be any plans to support it? Meanwhile, if needed, I can definitely help with the details of implementation.

Documentation is broken

The documentation page currently contains nothing but “Missing docstring” messages. I can also replicate this in a downloaded version of the package, and can't access any documentation from inside the REPL either.

perspective

From a preliminary glance, the package has at its core the notion of a parameterized abstraction of Points where concrete points that share the same dimensionality and use the same numeric type for coordinates work with the same set of methods in the same computational manner.

Once one has constructible points of given dimension and coordinate type, the usual next geometric step uses an ordered pair of points to signify and represent a directed line segment. The direction is given by the order of the two points, where the first point determines the initial position from which directionality is taken in the direction of the second position. The pair of points capture an extent, which is given by the distance of their separation (conventionally determined using the 2-norm, where required or just helpful different metrics ought be available and customizable).

The first point of the pair has the role of root or origin for the finite directed segment. The second point of the pair has the role of delimiting extent and carrying the [first point relative] direction. The first point is as the feathers of an arrow's tail and the second point is as the point of an arrow's head.

Once one has constructable finite directed line segments, the usual next step is to introduce rays, rooted at the first point and directed through the second point and of unlimited extent. The direction of a ray is oriented along the direction attached with the ray's generative finite segment. There is an associated opposite segment which obtains by swapping the order of the first and second points. With this opposite segment there is constructable the opposite ray (opposite to the ray constructed from the original segment).

By combining, simply taking these two rays together, we have the information necessary to the construction of a line. The line remembers the finite line segment underneath its construction, and in that respect, lines have a mercurial quality. The line keeps all its abstract character and all of its specific aspects while the underlying segment may travel along the line in either the originally given direction or in the opposite direction.

As long the relative position (first, last) of the originating points is maintained, lines are usefully intrinsically oriented. For many uses, this intrinsic lineal orientation may be ignored. For constructive purposes and to obtain more robust and consistent numerics, this orientation is computationally helpful.

When constructing a triangle in a plane as the interior of three escribed lines, pairwise nonparallel and noncoincident, the triangle will be oriented in concert with circulation of the bounding line's connective direction. This does mean that if one of the three bounding lines is directionally discordant with the other two, its opposite must be used (and either explicitly or implicitly maintained within in the triangle's construct).

Given a finite line segment, we can obtain a third point, for simplicity and to make the calculations more obvious, we utilize the midpoint or some other easily obtained internal point of that segment. At that location it is important to be able to construct a fourth point that is not on the line segment yet is perpendicular to it with that perpendicular going through (emanating from) the third point.

In 2D space, place the fourth point on the side of the segment found by a quarter turn from the directed segment (like the minute hand of a clock is centered at the third point and 12 is located at the segment's arrow head, the segment's second point) counterclockwise. The turn is counterclockwise simply to conform to the usual 2D X, Y orientation; we find positive angles from 0..pi/2 by rotating about the origin from positive X toward positive Y. In 3D we follow the "right hand rule" and in higher dimensions we continue that pattern.

Back to 2D. We have located the half plane into which the fourth point goes and we have located the ray (half line, as it were) onto which the fourth point is placed [the perpendicular to the original segment at the midpoint, directed into the identified halfplane]. We have the first point of a segment (the midpoint) and we have a direction. All that remains is to associate an extent and then the fourth point is wholly constructable. Take half of the original segment's extent as the extent from the third to the fourth point. (The "other half" of the extent would be on the other side of the original segment, and that may come in handy .. so, we preserve it by only using half of that extent to position the fourth point.)

With the third and fourth points, we construct a finite directed line segment. Using the first, second and fourth points we may construct an oriented triangle where those points are the vertices and are ordered as given. Where an isosceles triangle is desired, place a fifth point in the direction of the new segment, locating it by using the same extent again. The construct the triangle from the first, second and fifth points.

That triangle, itself or a homeomorphism thereof, also gives the halfplane to which it associates (point1..point2 is the base delimiting the plane, the apex at point3 is the direction of cover). Just as we constructed the opposite associated with the original directed line segment, we can construct the opposite triangle and so obtain the opposite halfplane. By combining halfplanes, we obtain an entire plane.

This does generalize to 3D and 4D, and one may continue the patterning into higher dimensions (better than following a pattern without knowing why, adopting the relations of a more formal geometric algebra will keep our logic consistent, relationships intact, and results reproducible.) Geometric intrinsics and algebraic elaborations obtain.

StructArrays interface for points

When calling into C functions with large arrays of points, it would be useful to have mutable StructArrays of points. As far as I understand it, this would just involve defining a static schema for Points.

Would this be a wanted feature? Also, how would we handle vectors of n-dimensional Points?

GB.Meta() errors for Multi Geometries

For Geometries like MultiPoint, MultiPolygon the following error occurs,

julia> using GeometryBasicsjulia> p1 = Point(3, 1)
2-element Point{2,Int64} with indices SOneTo(2):
3
1
julia> poi = meta(p1, city="Abuja", rainfall=1221.2)
2-element PointMeta{2,Int64,Point{2,Int64},(:city, :rainfall),Tuple{String,Float64}} with indices SOneTo(2):
3
1
julia> MultiPoint([p1])
1-element MultiPoint{2,Int64,Point{2,Int64},Array{Point{2,Int64},1}}:
[3, 1]
julia> meta(MultiPoint([p1]), city="Abuja", rainfall=1221.2)
ERROR: Metadata needs to be an array with the same length as data items. Found: String

also passing the metadata as a NamedTuple reproduces the error,

julia> using GeometryBasics; const GB = GeometryBasics
GeometryBasics

julia> tup = (featurecla = "Land", min_zoom = 0, scalerank = 0)
(featurecla = "Land", min_zoom = 0, scalerank = 0)

julia> polys = [Polygon(rand(Point{2, Float32}, 20)) for i in 1:10];

julia> multipoly = MultiPolygon(polys; tup...)  
ERROR: Metadata needs to be an array with the same length as data items. Found: String

Question about type parameters

If you take for instance the definition of LineString:
https://github.com/SimonDanisch/GeometryBasics.jl/blob/2a36f67effe9cc623ac82f09ce76c7c66007222c/src/basic_types.jl#L169-L175

I get that Dim and T should be there, representing the number of dimensions and type of the coordinates.

But P, the type of Point, and V, the the type of the vector of points, seem not needed. Why are they there? Do you need them to enforce something?

Also looking at these two:
https://github.com/SimonDanisch/GeometryBasics.jl/blob/2a36f67effe9cc623ac82f09ce76c7c66007222c/src/basic_types.jl#L133-L134
You use mainly LineP, but can't we do without?

Sorry if it's a bit basic but I'd like to get a good handle on this.

Do not define `Base.in` instead of `Base.issubset` (taking set theory seriously)

The definition at

"""
in(b1::Rect, b2::Rect)
Check if Rect `b1` is contained in `b2`. This does not use
strict inequality, so Rects may share faces and this will still
return true.
"""
function Base.in(b1::Rect{N}, b2::Rect{N}) where {N}
for i in 1:N
maximum(b1)[i] <= maximum(b2)[i] && minimum(b1)[i] >= minimum(b2)[i] || return false
end
return true
end

is not in the spirit of Base.in, and instead that procedure should be a method for Base.issubset ().

see for example:

https://github.com/invenia/Intervals.jl/blob/168565ef0a40d022bfe4030d0178cb1b83fff6cb/src/interval.jl#L370-L375

Base.in(a, b::AbstractInterval) = !(a  b || a  b)

function Base.in(a::AbstractInterval, b::AbstractInterval)
    # Intervals should be compared with set operations
    throw(ArgumentError("Intervals can not be compared with `in`. Use `issubset` instead."))
end

These operations are related, even graphically (∈ ⊂), but should not be mixed up.

Long type info

The type details for many geometries tend to be quite detailed, this causes problems while printing the data or representing it in a tabular form like dataframe that omits printing of data due to the long type names of GB columns. It'd be nice to have something like a Base.show() to sort of truncate the unnecessary info.

Matrix * Rectangle returns strange results

julia> SA[1 1;1 -1]*GeometryBasics.Rect2D((0,0),(1,1))
GeometryBasics.HyperRectangle{2,Int64}([0,-1], [2,2])

This should return a rotated (and scaled) rectangle. Instead it returns a bounding box for the rotated rectangle. I understand that a bounding box for the rotated rectangle might be useful, but this definitively not a matrix multiplication.

Invalid keyword argument syntax in latest version

The latest version (v0.3.6) has a few keyword syntax errors that cause a LoadError when I try to load the package.

julia> ]

(@v1.4) pkg> activate LDrawParser
 Activating environment at `~/.julia/dev/LDrawParser/Project.toml`

(LDrawParser) pkg> status GeometryBasics
Project LDrawParser v0.1.0
Status `~/.julia/dev/LDrawParser/Project.toml`
  [5c1252a2] GeometryBasics v0.3.6

julia> using GeometryBasics
[ Info: Precompiling GeometryBasics [5c1252a2-5f33-56bf-86c9-59e7332b4326]
ERROR: LoadError: LoadError: syntax: invalid keyword argument syntax "pointtype"
Stacktrace:
 [1] top-level scope at /home/kylebrown/.julia/packages/GeometryBasics/bMGVD/src/meshes.jl:100
 [2] include(::Module, ::String) at ./Base.jl:377
 [3] include(::String) at /home/kylebrown/.julia/packages/GeometryBasics/bMGVD/src/GeometryBasics.jl:1
 [4] top-level scope at /home/kylebrown/.julia/packages/GeometryBasics/bMGVD/src/GeometryBasics.jl:22
 [5] include(::Module, ::String) at ./Base.jl:377
 [6] top-level scope at none:2
 [7] eval at ./boot.jl:331 [inlined]
 [8] eval(::Expr) at ./client.jl:449
 [9] top-level scope at ./none:3
in expression starting at /home/kylebrown/.julia/packages/GeometryBasics/bMGVD/src/meshes.jl:100
in expression starting at /home/kylebrown/.julia/packages/GeometryBasics/bMGVD/src/GeometryBasics.jl:22
ERROR: Failed to precompile GeometryBasics [5c1252a2-5f33-56bf-86c9-59e7332b4326] to /home/kylebrown/.julia/compiled/v1.4/GeometryBasics/lB452_nEEYK.ji.
Stacktrace:
 [1] error(::String) at ./error.jl:33
 [2] compilecache(::Base.PkgId, ::String) at ./loading.jl:1272
 [3] _require(::Base.PkgId) at ./loading.jl:1029
 [4] require(::Base.PkgId) at ./loading.jl:927
 [5] require(::Module, ::Symbol) at ./loading.jl:922
 [6] eval(::Module, ::Any) at ./boot.jl:331
 [7] eval_user_input(::Any, ::REPL.REPLBackend) at /buildworker/worker/package_linux64/build/usr/share/julia/stdlib/v1.4/REPL/src/REPL.jl:86
 [8] run_backend(::REPL.REPLBackend) at /home/kylebrown/.julia/packages/Revise/qxX5H/src/Revise.jl:1186
 [9] top-level scope at none:0

`AbstractMesh` is just `AbstractVector`

I'm working on porting MeshCat.jl from GeometryTypes to GeometryBasics, and I just ran into an interesting method ambiguity that turned out to be due to the fact that AbstractMesh is exactly AbstractVector:

julia> AbstractMesh
AbstractArray{Element,1} where Element

julia> AbstractMesh == AbstractVector
true

This makes it impossible to write a function that dispatches on a general AbstractVector vs. an AbstractMesh. It also means that any function written for an AbstractMesh is actually claiming to support any kind of vector of any element, which seems wrong.

It's not a blocker for my work, but I think it's likely to be a minor source of annoyance going forward. Would it be possible to put some kind of type restriction on the element of AbstractMesh? Anything more restrictive than Any would fix the issue and make AbstractMesh a usable type.

Area of 2D triangle can be negative

For example:

julia> area(Point2f0[[0,0], [0,1], [1,1]])
-0.5f0

The relevant area method is here:

function area(contour::AbstractVector{<:AbstractPoint{N,T}}) where {N,T}

I think there are two issues with this method:

  1. It's the generic implementation for N dimensions, but it only works for N=2, as it assumes that cross exists and returns a scalar.
  2. It's missing an abs call on the result.

Regarding the first point: there's already a specific method for N=3, and we can make one for N=2. Do we need a generic method for N>3?

Polygon is sequence of segments, not points?

struct Polygon{
Dim, T <: Real,
P <: AbstractPoint{Dim, T},
L <: AbstractVector{<: LineP{Dim, T, P}},
V <: AbstractVector{L}
} <: AbstractPolygon{Dim, T}
exterior::L
interiors::V
end

am I understanding correctly that the Polygon type is represented by an AbstractVector with eltype as LineP? I would have expected an eltype of <:AbstractPoint like in Ngon.

Rename Point3f0 and similar to Point3f, etc.

I'd like to make a case against the suffix 0 in types such as Point3f0:

Is there a reason to prefer Point3f0?

decompose returns unexpected result

I wanted to mesh an arbitrary polygons from 3d points. But if provided points are in CW direction, mesh() returns empty or with missing geometry faces. I guess it's because decompose() function returns empty array:

tri_ccw = Point{3,Float64}[(0,0,0,), (1,0,0), (0,1,0)] # points in ccw direction
tri_cw = reverse(tri_ccw) # points in cw direction
@show length(decompose(TriangleFace{Int}, tri_ccw)) == 1 # <- as expected
@show length(decompose(TriangleFace{Int}, tri_cw )) == 1 # <- decompose returns with empty array

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.