Skip to content

Commit b755541

Browse files
committed
Fully functional regridding
TODO: multithread, spherical
1 parent ea81397 commit b755541

File tree

1 file changed

+118
-0
lines changed

1 file changed

+118
-0
lines changed
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import GeoInterface as GI, GeometryOps as GO
2+
using SortTileRecursiveTree: STRtree
3+
using SparseArrays: spzeros
4+
using Extents
5+
6+
using CairoMakie, GeoInterfaceMakie
7+
8+
include("sphericalpoints.jl")
9+
10+
function area_of_intersection_operator(grid1, grid2; nodecapacity1 = 10, nodecapacity2 = 10)
11+
area_of_intersection_operator(GO.Planar(), grid1, grid2; nodecapacity1 = nodecapacity1, nodecapacity2 = nodecapacity2)
12+
end
13+
14+
function area_of_intersection_operator(m::GO.Manifold, grid1, grid2; nodecapacity1 = 10, nodecapacity2 = 10) # grid1 and grid2 are both vectors of polygons
15+
A = spzeros(Float64, length(grid1), length(grid2))
16+
# Prepare STRtrees for the two grids, to speed up intersection queries
17+
# we may want to separately tune nodecapacity if one is much larger than the other.
18+
# specifically we may want to tune leaf node capacity via Hilbert packing while still
19+
# constraining inner node capacity. But that can come later.
20+
tree1 = STRtree(grid1; nodecapacity = nodecapacity1)
21+
tree2 = STRtree(grid2; nodecapacity = nodecapacity2)
22+
# Do the dual query, which is the most efficient way to do this,
23+
# by iterating down both trees simultaneously, rejecting pairs of nodes that do not intersect.
24+
# when we find an intersection, we calculate the area of the intersection and add it to the result matrix.
25+
GO.SpatialTreeInterface.do_dual_query(Extents.intersects, tree1, tree2) do i1, i2
26+
p1, p2 = grid1[i1], grid2[i2]
27+
# may want to check if the polygons intersect first,
28+
# to avoid antimeridian-crossing multipolygons viewing a scanline.
29+
intersection_polys = try # can remove this now, got all the errors cleared up in the fix.
30+
# At some future point, we may want to add the manifold here
31+
# but for right now, GeometryOps only supports planar polygons anyway.
32+
GO.intersection(p1, p2; target = GI.PolygonTrait())
33+
catch e
34+
@error "Intersection failed!" i1 i2
35+
rethrow(e)
36+
end
37+
38+
area_of_intersection = GO.area(m, intersection_polys)
39+
if area_of_intersection > 0
40+
A[i1, i2] += area_of_intersection
41+
end
42+
end
43+
44+
return A
45+
end
46+
47+
grid1 = begin
48+
gridpoints = [(i, j) for i in 0:2, j in 0:2]
49+
[GI.Polygon([GI.LinearRing([gridpoints[i, j], gridpoints[i, j+1], gridpoints[i+1, j+1], gridpoints[i+1, j], gridpoints[i, j]])]) for i in 1:size(gridpoints, 1)-1, j in 1:size(gridpoints, 2)-1] |> vec
50+
end
51+
52+
grid2 = begin
53+
diamondpoly = GI.Polygon([GI.LinearRing([(0, 1), (1, 2), (2, 1), (1, 0), (0, 1)])])
54+
trianglepolys = GI.Polygon.([
55+
[GI.LinearRing([(0, 0), (1, 0), (0, 1), (0, 0)])],
56+
[GI.LinearRing([(0, 1), (0, 2), (1, 2), (0, 1)])],
57+
[GI.LinearRing([(1, 2), (2, 1), (2, 2), (1, 2)])],
58+
[GI.LinearRing([(2, 1), (2, 0), (1, 0), (2, 1)])],
59+
])
60+
[diamondpoly, trianglepolys...]
61+
end
62+
63+
A = area_of_intersection_operator(grid1, grid2)
64+
65+
# Now, let's perform some interpolation!
66+
area1 = vec(sum(A, dims=2))
67+
# test: @assert area1 == GO.area.(grid1)
68+
area2 = vec(sum(A, dims=1))
69+
# test: @assert area2 == GO.area.(grid2)
70+
71+
values_on_grid2 = [0, 0, 5, 0, 0]
72+
poly(grid2; color = values_on_grid2, strokewidth = 2, strokecolor = :red)
73+
74+
values_on_grid1 = A * values_on_grid2 ./ area1
75+
@assert sum(values_on_grid1 .* area1) == sum(values_on_grid2 .* area2)
76+
poly(grid1; color = values_on_grid1, strokewidth = 2, strokecolor = :blue)
77+
78+
values_back_on_grid2 = A' * values_on_grid1 ./ area2
79+
@assert sum(values_back_on_grid2 .* area2) == sum(values_on_grid2 .* area2)
80+
poly(grid2; color = values_back_on_grid2, strokewidth = 2, strokecolor = :green)
81+
# We can see here that some data has diffused into the central diamond cell of grid2,
82+
# since it was overlapped by the top left cell of grid1.
83+
84+
85+
using SpeedyWeather
86+
using GeoMakie
87+
88+
SpeedyWeatherGeoMakieExt = Base.get_extension(SpeedyWeather, :SpeedyWeatherGeoMakieExt)
89+
90+
grid1 = rand(OctaHEALPixGrid, 5 + 100)
91+
grid2 = rand(FullGaussianGrid, 4 + 100)
92+
93+
faces1 = SpeedyWeatherGeoMakieExt.get_faces(grid1)
94+
faces2 = SpeedyWeatherGeoMakieExt.get_faces(grid2)
95+
96+
polys1 = GI.Polygon.(GI.LinearRing.(eachcol(faces1))) .|> GO.fix
97+
polys2 = GI.Polygon.(GI.LinearRing.(eachcol(faces2))) .|> GO.fix
98+
99+
A = @time area_of_intersection_operator(polys1, polys2)
100+
101+
p1 = polys1[93]
102+
p2 = polys2[105]
103+
104+
f, a, p = poly(p1)
105+
poly!(a, p2)
106+
f
107+
108+
# bug found in Foster Hormann tracing
109+
# but geos also does the same thing
110+
boxpoly = GI.Polygon([GI.LinearRing([(0, 0), (2, 0), (2, 2), (0, 2), (0, 0)])])
111+
diamondpoly = GI.Polygon([GI.LinearRing([(0, 1), (1, 2), (2, 1), (1, 0), (0, 1)])])
112+
113+
diffpoly = GO.difference(boxpoly, diamondpoly; target = GI.PolygonTrait()) |> only
114+
cutpolys = GO.cut(diffpoly, GI.Line([(0, 0), (4, 0)])) # even cut misbehaves!
115+
116+
117+
118+

0 commit comments

Comments
 (0)