juliageometry / geometrybasics.jl Goto Github PK
View Code? Open in Web Editor NEWBasic Geometry Types
License: MIT License
Basic Geometry Types
License: MIT License
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?
What is the benefit of them being AbstractVectors?
In AlgebraOfGraphics we would like to support all shapes that are plottable. We would like to just implement methods for AbstractGeometry. Instead we have to define methods for Multi* types separately.
MakieOrg/AlgebraOfGraphics.jl#80
related to #70
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
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?
In offsetintegers.jl, for the basic operators loops,
@eval Base.$op(x::T) where {T <: OffsetInteger} = T($(op)(value(x)))
value not defined
error is thrown.
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
I was wondering if there is any library which can be used to plot shapes defined in GeometryBasics?
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.
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
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?
@JuliaRegistrator register()
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!
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.
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
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
GeometryBasics.jl/src/geometry_primitives.jl
Line 155 in e9c9e0c
This can be avoided by passing a normaltype
to mesh
(which causes it to call decompose).
The definitions at
GeometryBasics.jl/src/basic_types.jl
Lines 47 to 49 in 234699e
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
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
iterate
iterate
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.
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).
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.
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]),))
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
see stacktrace starting here
Before its lost on slack:
@mohamed82008 suggests to respect http://victorsndvg.github.io/FEconv/formats/vtk.xhtml
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
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?
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
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!
Usedecompose(ConcretePointType, geometry)
to getVector{ConcretePointType}
with
ConcretePointType
to be something likePoint{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]
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
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
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.
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
.
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
?
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")
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.
there are some warnings about missing docs or duplicated docstrings, see eg. here
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.
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.
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?
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
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.
The definition at
GeometryBasics.jl/src/primitives/rectangles.jl
Lines 469 to 481 in 234699e
is not in the spirit of Base.in
, and instead that procedure should be a method for Base.issubset
(⊆
).
see for example:
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.
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.
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.
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
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.
For example:
julia> area(Point2f0[[0,0], [0,1], [1,1]])
-0.5f0
The relevant area
method is here:
GeometryBasics.jl/src/triangulation.jl
Line 36 in 24d0ee5
I think there are two issues with this method:
cross
exists and returns a scalar.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?
GeometryBasics.jl/src/primitives/spheres.jl
Lines 34 to 39 in 2580374
is this intentional?
GeometryBasics.jl/src/basic_types.jl
Lines 232 to 240 in dfb0083
Polygon
type is represented by an AbstractVector
with eltype
as LineP
? I would have expected an eltype
of <:AbstractPoint
like in Ngon
.I'd like to make a case against the suffix 0
in types such as Point3f0
:
1.2f0
, the f
denotes Float32
and 0
is an exponent. 1.2f4
is also a Float32.Is there a reason to prefer Point3f0?
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
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.