Skip to content

Commit d54972c

Browse files
committed
Add rotation examples for transform
Signed-off-by: Avnish Jaltare <avnishjaltare8@gmail.com>
1 parent 9de9c8e commit d54972c

3 files changed

Lines changed: 95 additions & 3 deletions

File tree

docs/src/tutorials/creating_geometry.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,40 @@ plot!(polygon1)
113113
fig
114114
````
115115

116+
We can also rotate a polygon with the same `transform` function. A `LinearMap`
117+
rotates around the origin.
118+
119+
````@example creating_geometry
120+
theta = π / 4
121+
rotation = CoordinateTransformations.LinearMap([
122+
cos(theta) -sin(theta)
123+
sin(theta) cos(theta)
124+
]);
125+
polygon1_rotated_origin = GO.transform(rotation, polygon1);
126+
127+
fig_rotation = Figure()
128+
ax_origin = Axis(fig_rotation[1, 1]; title = "Rotate around the origin", aspect = DataAspect())
129+
poly!(ax_origin, polygon1; color = (:steelblue, 0.5), strokecolor = :steelblue)
130+
poly!(ax_origin, polygon1_rotated_origin; color = (:orange, 0.35), strokecolor = :orange)
131+
fig_rotation
132+
````
133+
134+
To rotate around a polygon's centroid instead, compose the rotation with
135+
translations before and after it.
136+
137+
````@example creating_geometry
138+
polygon1_centroid = GO.centroid(polygon1)
139+
rotation_about_centroid = CoordinateTransformations.Translation(polygon1_centroid...) ∘
140+
rotation ∘
141+
CoordinateTransformations.Translation((-).(polygon1_centroid)...)
142+
polygon1_rotated_centroid = GO.transform(rotation_about_centroid, polygon1);
143+
144+
ax_centroid = Axis(fig_rotation[1, 2]; title = "Rotate around the centroid", aspect = DataAspect())
145+
poly!(ax_centroid, polygon1; color = (:steelblue, 0.5), strokecolor = :steelblue)
146+
poly!(ax_centroid, polygon1_rotated_centroid; color = (:orange, 0.35), strokecolor = :orange)
147+
fig_rotation
148+
````
149+
116150
Polygons can contain "holes". The first `LinearRing` in a polygon is the exterior, and all subsequent `LinearRing`s are treated as holes in the leading `LinearRing`.
117151

118152
`GeoInterface` offers the `GI.getexterior(poly)` and `GI.gethole(poly)` methods to get the exterior ring and an iterable of holes, respectively.

src/transformations/transform.jl

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ This uses [`apply`](@ref), so will work with any geometry, vector of geometries,
2626
2727
Apply a function `f` to all the points in `obj`.
2828
29-
Points will be passed to `f` as an `SVector` to allow
30-
using CoordinateTransformations.jl and Rotations.jl
31-
without hassle.
29+
Points are passed to `f` as an `SVector`, so `f` can be a plain function
30+
or a callable transform from CoordinateTransformations.jl, such as
31+
`Translation`, `LinearMap`, or a composition of transforms.
32+
33+
Because this uses [`apply`](@ref) internally, it works with polygons,
34+
multipolygons, arrays of geometries, feature collections, and tables.
3235
3336
`SVector` is also a valid GeoInterface.jl point, so will
3437
work in all GeoInterface.jl methods.
@@ -52,6 +55,37 @@ re.SVector{2, Float64}[[4.5, 3.5], [6.5, 5.5], [8.5, 7.5], [4.5, 3.5]], nothing,
5255
rraysCore.SVector{2, Float64}[[6.5, 5.5], [8.5, 7.5], [9.5, 8.5], [6.5, 5.5]], nothing, nothing)], nothing, nothing)
5356
```
5457
58+
CoordinateTransformations.jl also works directly with callable transforms like
59+
`LinearMap`, which is handy for 2D rotation.
60+
61+
```julia
62+
julia> rotation_geom = GI.Polygon([[(0.0, 0.0), (2.0, 0.0), (2.0, 1.0), (0.0, 1.0), (0.0, 0.0)]]);
63+
64+
julia> rotation = CoordinateTransformations.LinearMap([0.0 -1.0; 1.0 0.0]);
65+
66+
julia> rotated = GO.transform(rotation, rotation_geom);
67+
68+
julia> Tuple.(GI.getpoint(GI.getexterior(rotated)))
69+
5-element Vector{Tuple{Float64, Float64}}:
70+
(0.0, 0.0)
71+
(0.0, 2.0)
72+
(-1.0, 2.0)
73+
(-1.0, 0.0)
74+
(0.0, 0.0)
75+
76+
julia> center = GO.centroid(rotation_geom);
77+
78+
julia> rotated_centroid = GO.transform(
79+
CoordinateTransformations.Translation(center...) ∘
80+
rotation ∘
81+
CoordinateTransformations.Translation((-).(center)...),
82+
rotation_geom,
83+
);
84+
85+
julia> all(GO.centroid(rotated_centroid) .≈ center)
86+
true
87+
```
88+
5589
With Rotations.jl you need to actually multiply the Rotation
5690
by the `SVector` point, which is easy using an anonymous function.
5791

test/transformations/transform.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ using ..TestHelpers
66

77
geom = GI.Polygon([GI.LinearRing([(1, 2), (3, 4), (5, 6), (1, 2)]),
88
GI.LinearRing([(3, 4), (5, 6), (6, 7), (3, 4)])])
9+
rotation_geom = GI.Polygon([GI.LinearRing([(1.0, 1.0), (3.0, 1.0), (3.0, 2.0), (1.0, 2.0), (1.0, 1.0)])])
910

1011
@testset_implementations "transform" begin
1112
translated = GI.Polygon([GI.LinearRing([[4.5, 3.5], [6.5, 5.5], [8.5, 7.5], [4.5, 3.5]]),
@@ -27,3 +28,26 @@ end
2728
@test GI.is3d(geom_transformed)
2829
@test !GI.ismeasured(geom_transformed)
2930
end
31+
32+
@testset_implementations "transform polygon rotation around the origin" begin
33+
rotation = LinearMap([0.0 -1.0; 1.0 0.0])
34+
rotated = GO.transform(rotation, $rotation_geom)
35+
expected_points = [
36+
(-1.0, 1.0), (-1.0, 3.0), (-2.0, 3.0), (-2.0, 1.0), (-1.0, 1.0),
37+
]
38+
rotated_points = map(collect(GO.flatten(GI.PointTrait, rotated))) do p
39+
(GI.x(p), GI.y(p))
40+
end
41+
@test rotated_points == expected_points
42+
end
43+
44+
@testset_implementations "transform polygon rotation around centroid preserves centroid and area" begin
45+
rotation = LinearMap([0.0 -1.0; 1.0 0.0])
46+
center = GO.centroid($rotation_geom)
47+
rotated = GO.transform(
48+
Translation(center...) rotation Translation((-).(center)...),
49+
$rotation_geom,
50+
)
51+
@test all(GO.centroid(rotated) .≈ center)
52+
@test GO.area(rotated) GO.area($rotation_geom)
53+
end

0 commit comments

Comments
 (0)