|
| 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