Skip to content

Commit 892688d

Browse files
authored
Fix and test rasterize for feature collections (#651)
* fix rasterize points * fix rasterize * bugfix * test
1 parent 9b2d396 commit 892688d

File tree

2 files changed

+76
-59
lines changed

2 files changed

+76
-59
lines changed

src/methods/rasterize.jl

+48-45
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ function Rasterizer(data::T; fill, geomcolumn=nothing, kw...) where T
148148
schema = Tables.schema(data)
149149
cols = Tables.columns(data)
150150
colnames = Tables.columnnames(Tables.columns(data))
151-
fillitr = _iterable_fill(cols, fill)
151+
fillitr = _iterable_fill(nothing, cols, fill)
152152
# If fill is a symbol or tuple of Symbol we need to allocate based on the column type
153153
geomcolname = if isnothing(geomcolumn)
154154
geomcols = GI.geometrycolumns(data)
@@ -171,27 +171,27 @@ function Rasterizer(data::T; fill, geomcolumn=nothing, kw...) where T
171171
Rasterizer(GeoInterface.trait(data), data; fill, kw...)
172172
end
173173
end
174-
function Rasterizer(::GI.AbstractFeatureCollectionTrait, fc; name, fill, kw...)
175-
# TODO: how to handle when there are fillvals with different types
176-
# fillval = _featurefillval(GI.getfeature(fc, 1), fill)
177-
fillitr = _iterable_fill(fc, fill)
178-
geometries = map(f -> GI.geometry(f), GI.getfeature(fc))
179-
Rasterizer(geometries; kw..., fill, fillitr)
174+
function Rasterizer(trait::GI.AbstractFeatureCollectionTrait, fc; fill, kw...)
175+
fillitr = _iterable_fill(trait, fc, fill)
176+
geoms = map(f -> GI.geometry(f), GI.getfeature(fc))
177+
Rasterizer(geoms, fill, fillitr; kw...)
180178
end
181-
function Rasterizer(::GI.AbstractFeatureTrait, feature; fill, kw...)
182-
fillitr = _iterable_fill(feature, fill)
183-
# fillval = _featurefillval(feature, fill)
184-
Rasterizer(GI.geometry(feature), fill, fillitr; kw...)
179+
function Rasterizer(trait::GI.AbstractFeatureTrait, feature; fill, kw...)
180+
fillitr = _iterable_fill(trait, feature, fill)
181+
geom = GI.geometry(feature)
182+
Rasterizer(geom, fill, fillitr; kw...)
185183
end
186-
function Rasterizer(::GI.GeometryCollectionTrait, collection; kw...)
187-
Rasterizer(collect(GI.getgeom(collection)); kw...)
184+
function Rasterizer(trait::GI.GeometryCollectionTrait, collection; kw...)
185+
geoms = collect(GI.getgeom(collection))
186+
Rasterizer(geoms; kw...)
188187
end
189-
function Rasterizer(::Nothing, geoms; fill, kw...)
190-
fillitr = _iterable_fill(geoms, fill)
188+
function Rasterizer(trait::Nothing, geoms; fill, kw...)
189+
fillitr = _iterable_fill(trait, geoms, fill)
191190
Rasterizer(geoms, fill, fillitr; kw...)
192191
end
193-
function Rasterizer(::GI.AbstractGeometryTrait, geom; fill, kw...)
194-
Rasterizer(geom, fill, _iterable_fill(geom, fill); kw...)
192+
function Rasterizer(trait::GI.AbstractGeometryTrait, geom; fill, kw...)
193+
fillitr = _iterable_fill(trait, geom, fill)
194+
Rasterizer(geom, fill, fillitr; kw...)
195195
end
196196

197197
function get_eltype_missingval(eltype, missingval, fill, fillitr, init, filename, op, reducer)
@@ -274,51 +274,53 @@ function _filter_name(name, fill)
274274
end
275275

276276
# A Tuple of `Symbol` is multiple keys to make a RasterStack
277-
_iterable_fill(data, keys::Tuple{Symbol,Vararg}) =
278-
NamedTuple{keys}(map(k -> _iterable_fill(data, k), keys))
277+
_iterable_fill(trait, data, keys::Tuple{Symbol,Vararg}) =
278+
NamedTuple{keys}(map(k -> _iterable_fill(trait, data, k), keys))
279279
# A Symbol is a Table or FeatureCollection key, it cant be used as fill itself
280-
function _iterable_fill(data, key::Symbol)
280+
function _iterable_fill(trait, data, key::Symbol)
281281
if GI.isfeature(data)
282-
return get(x -> error("feature has no key $key"), GI.properties(data), key)
282+
return get(() -> throw(ArgumentError("feature has no property `:$key`")), GI.properties(data), key)
283+
elseif trait isa GI.FeatureCollectionTrait
284+
return [get(() -> throw(ArgumentError("feature has no property `:$key`")), GI.properties(f), key) for f in GI.getfeature(data)]
283285
end
284286
cols = Tables.columns(data)
285287
# For column tables, get the column now
286288
names = Tables.columnnames(cols)
287289
key in names || _fill_key_error(names, key)
288290
return Tables.getcolumn(cols, key)
289291
end
290-
_iterable_fill(data, fill::Function) = fill
291-
_iterable_fill(data, fill::NamedTuple) = begin
292-
map(f -> _iterable_fill(data, f), fill)
292+
_iterable_fill(trait, data, fill::Function) = fill
293+
_iterable_fill(trait, data, fill::NamedTuple) = begin
294+
map(f -> _iterable_fill(trait, data, f), fill)
293295
end
294-
# Inspect our data and fill as much as possible to check they match
295-
# and cycle any fill of known length one
296-
function _iterable_fill(data, fill)
297-
if GI.isgeometry(data) || GI.isfeature(data)
296+
function _iterable_fill(trait, data, fill)
297+
# trait isa Union{GI.AbstractGeometryTrait,GI.FeatureTrait} && return fill
298+
if trait isa GI.AbstractGeometryTrait || trait isa GI.FeatureTrait
298299
return fill
299-
end
300-
if fill isa Number
300+
elseif fill isa Number
301301
return Iterators.cycle(fill)
302-
end
303-
if Tables.istable(typeof(data))
302+
elseif Tables.istable(typeof(data))
304303
# we don't need the keys, just the column length
305304
data = first(Tables.columns(data))
306305
end
307-
if Base.IteratorSize(data) isa Union{Base.HasShape,Base.HasLength}
308-
fillvec = collect(fill)
309-
l = length(fillvec)
306+
307+
if trait isa GI.FeatureCollectionTrait
308+
n = GI.nfeature(data)
309+
elseif Base.IteratorSize(data) isa Union{Base.HasShape,Base.HasLength}
310310
n = length(data)
311-
if l == 1
312-
# Cycle all length one iterables to fill every row
313-
return Iterators.cycle(fillvec[1])
314-
elseif !(l == n)
315-
throw(ArgumentError("Length of fill $l does not match length of iterator $n"))
316-
else
317-
return fillvec
318-
end
319311
else
320312
return fill
321313
end
314+
fillvec = collect(fill)
315+
l = length(fillvec)
316+
if l == 1
317+
# Cycle all length one iterables to fill every row
318+
return Iterators.cycle(fillvec[1])
319+
elseif !(l == n)
320+
throw(ArgumentError("Length of fill $l does not match length of iterator $n"))
321+
else
322+
return fillvec
323+
end
322324
end
323325

324326
_getfill(itrs::NamedTuple, i::Int) = map(itr -> _getfill(itr, i), itrs)
@@ -660,9 +662,10 @@ end
660662
_rasterize_points!(A, r::Rasterizer) = _rasterize_points!(A, r.geom, r.fillitr, r)
661663
_rasterize_points!(A, geom, fillitr, r::Rasterizer) =
662664
_rasterize_points!(A, GI.trait(geom), geom, fillitr, r)
663-
function _rasterize_points!(A, ::GI.AbstractGeometryTrait, geom, fill, r::Rasterizer)
665+
666+
function _rasterize_points!(A, trait::GI.AbstractGeometryTrait, geom, fill, r::Rasterizer)
664667
points = GI.getpoint(geom)
665-
fill1 =_iterable_fill(points, fill)
668+
fill1 =_iterable_fill(nothing, points, fill)
666669
_rasterize_points!(A, nothing, points, fill1, r)
667670
end
668671
function _rasterize_points!(A, ::GI.GeometryCollectionTrait, collection, fill, r::Rasterizer)

test/rasterize.jl

+28-14
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using Rasters, Test, ArchGDAL, ArchGDAL.GDAL, Dates, Statistics, DataFrames, Extents, Shapefile, GeometryBasics
2-
import GeoInterface
2+
import GeoInterface as GI
33
using Rasters.Lookups, Rasters.Dimensions
44
using Rasters: bounds
55

@@ -25,11 +25,14 @@ multi_point = ArchGDAL.createmultipoint(pointvec)
2525
linestring = ArchGDAL.createlinestring(pointvec)
2626
multi_linestring = ArchGDAL.createmultilinestring([pointvec])
2727
linearring = ArchGDAL.createlinearring(pointvec)
28-
line_collection = GeoInterface.GeometryCollection([multi_linestring])
29-
poly_collection = GeoInterface.GeometryCollection([polygon])
30-
pointfc = map(GeoInterface.getpoint(polygon), vals) do geom, v
28+
line_collection = GI.GeometryCollection([multi_linestring])
29+
poly_collection = GI.GeometryCollection([polygon])
30+
pointtable = map(GI.getpoint(polygon), vals) do geom, v
3131
(geometry=geom, val1=v, val2=2.0f0v)
3232
end
33+
pointfc = map(GI.getpoint(polygon), vals) do geom, v
34+
GI.Feature(geom; properties=(val1=v, val2=2.0f0v))
35+
end |> GI.FeatureCollection
3336
table = (X=first.(pointvec), Y=last.(pointvec), othercol=zero.(last.(pointvec)))
3437

3538
test_shape_dir = realpath(joinpath(dirname(pathof(Shapefile)), "..", "test", "shapelib_testcases"))
@@ -48,10 +51,11 @@ st = RasterStack((A1, copy(A1)))
4851
geom = pointvec
4952
geom = line_collection
5053
geom = poly_collection
54+
geom = pointfc
5155
threaded = true
5256

5357
for A in (A1, A2),
54-
geom in (pointvec, pointfc, multi_point, linestring, multi_linestring, linearring, polygon, multi_polygon, table, line_collection, poly_collection),
58+
geom in (pointvec, pointtable, pointfc, multi_point, linestring, multi_linestring, linearring, polygon, multi_polygon, table, line_collection, poly_collection),
5559
threaded in (true, false)
5660

5761
fill!(A, 0)
@@ -64,15 +68,15 @@ st = RasterStack((A1, copy(A1)))
6468
@test sum(A) == 4.0
6569
@test sum(rasterize(last, geom; to=A, shape=:point, fill=1, missingval=0, threaded)) == 4.0
6670
fill!(A, 0)
67-
if !Tables.istable(geom)
71+
if !(Tables.istable(geom) || GI.isfeaturecollection(geom))
6872
rasterize!(count, A, [geom, geom]; shape=:point, threaded)
6973
@test sum(A) == 10.0
7074
fill!(A, 0)
7175
end
7276
end
7377
geom = multi_point
7478
for A in (A1, A2),
75-
geom in (table, pointvec, pointfc, multi_point, linestring, multi_linestring, linearring, polygon, multi_polygon, line_collection, poly_collection),
79+
geom in (table, pointvec, pointtable, pointfc, multi_point, linestring, multi_linestring, linearring, polygon, multi_polygon, line_collection, poly_collection),
7680
threaded in (true, false)
7781

7882
rasterize!(sum, st, geom; shape=:point, fill=(layer1=2, layer2=3), threaded)
@@ -94,7 +98,7 @@ st = RasterStack((A1, copy(A1)))
9498

9599

96100
for A in (A1, A2),
97-
geom in (table, pointvec, pointfc, multi_point, linestring, multi_linestring, linearring, polygon, multi_polygon),
101+
geom in (table, pointvec, pointtable, pointfc, multi_point, linestring, multi_linestring, linearring, polygon, multi_polygon),
98102
threaded in (true, false)
99103

100104
st[:layer1] .= 0; st[:layer2] .= 0
@@ -180,11 +184,11 @@ end
180184

181185
@testset "from geometries, tables and features of points" begin
182186
A = A1
183-
data = DataFrame(pointfc)
187+
data = DataFrame(pointtable)
184188
data = multi_point
185189
data = pointfc
186190

187-
for data in (pointfc, DataFrame(pointfc), multi_point, pointvec, reverse(pointvec))
191+
for data in (pointtable, pointfc, DataFrame(pointtable), multi_point, pointvec, reverse(pointvec))
188192
@test sum(skipmissing(rasterize(sum, data; to=A, fill=1))) == 5
189193
@testset "to and fill Keywords are required" begin
190194
@test_throws ArgumentError R = rasterize(data; fill=1)
@@ -206,8 +210,7 @@ end
206210
end
207211

208212
@testset "a single feature" begin
209-
feature = pointfc[4]
210-
GeoInterface.isfeature(feature)
213+
feature = pointtable[4]
211214
@testset "NTuple of Symbol fill makes an stack" begin
212215
rst = rasterize(feature; to=A, fill=(:val1, :val2))
213216
@test parent(rst.val1) isa Array{Union{Missing,Int},2}
@@ -225,9 +228,9 @@ end
225228
end
226229

227230
@testset "feature collection, table from fill of Symbol keys" begin
231+
data = pointtable
228232
data = pointfc
229-
Tables.istable(data)
230-
for data in (pointfc, DataFrame(pointfc)), threaded in (true, false)
233+
for data in (pointfc, pointtable, DataFrame(pointtable)), threaded in (true, false)
231234
@testset "NTuple of Symbol fill makes an stack" begin
232235
rst = rasterize(sum, data; to=A, fill=(:val1, :val2), threaded)
233236
@test parent(rst.val1) isa Array{Union{Missing,Int},2}
@@ -445,6 +448,17 @@ end
445448
end
446449
end
447450

451+
@testset "Rasterizing feature collection of polygons" begin
452+
p1 = GI.Polygon([[[-55965.680060140774, -31588.16072168928], [-55956.50771556479, -31478.09258677756], [-31577.548550575284, -6897.015828572996], [-15286.184961223798, -15386.952072224134], [-9074.387601621409, -27468.20712382156], [-8183.4538916097845, -31040.003969070774], [-27011.85123029944, -38229.02388009402], [-54954.72822634951, -32258.9734800704], [-55965.680060140774, -31588.16072168928]]])
453+
p2 = GI.Polygon([[[-80000.0, -80000.0], [-80000.0, 80000.0], [-60000.0, 80000.0], [-50000.0, 40000.0], [-60000.0, -80000.0], [-80000.0, -80000.0]]])
454+
fc = GI.FeatureCollection([GI.Feature(p1, properties = (;val=1)), GI.Feature(p2, properties = (;val=2))])
455+
456+
vecint = rasterize(sum, [p1, p2]; size=(100, 100), fill=[1, 2])
457+
fcint = rasterize(sum, fc; size=(100, 100), fill=[1, 2])
458+
fcval = rasterize(sum, fc; size=(100, 100), fill=:val)
459+
@test all(vecint .=== fcval .=== fcint)
460+
end
461+
448462
@testset "Rasterizing with extra dimensions" begin
449463
A3 = cat(A1, A1, A1; dims=Band)
450464
Rasters.rasterize!(last, A3, polygon; fill=true)

0 commit comments

Comments
 (0)