@@ -89,7 +89,7 @@ mutable struct LineString <: AbstractGeometry
8989 finalizer (destroyGeom, line)
9090 line
9191 end
92- # create a linestring from a list of coordiantes
92+ # create a linestring from a list of coordinates
9393 function LineString (coords:: Vector{Vector{Float64}} , context:: GEOSContext = get_global_context ())
9494 line = new (createLineString (coords, context), context)
9595 finalizer (destroyGeom, line)
@@ -116,7 +116,7 @@ mutable struct MultiLineString <: AbstractGeometry
116116 finalizer (destroyGeom, multiline)
117117 multiline
118118 end
119- # create a multilinestring from a list of linestring coordiantes
119+ # create a multilinestring from a list of linestring coordinates
120120 MultiLineString (multiline:: Vector{Vector{Vector{Float64}}} ,context:: GEOSContext = get_global_context ()) =
121121 MultiLineString (
122122 createCollection (
@@ -309,6 +309,31 @@ const geomtypes = [
309309 GeometryCollection,
310310]
311311
312+ const HasCoordSeq = Union{LineString, LinearRing, Point}
313+ """
314+
315+ coordinates!(out::Vector{Float64}, geo::$HasCoordSeq , i::Integer)
316+ coordinates!(out::Vector{Float64}, geo::Point)
317+
318+ Copy the coordinates of the ith point of geo into `out`.
319+ """
320+ function coordinates! (out, geo:: HasCoordSeq , i:: Integer , ctx:: GEOSContext = get_context (geo))
321+ GC. @preserve out geo begin
322+ seq = GEOSGeom_getCoordSeq_r (ctx, geo):: Ptr
323+ getCoordinates! (out, seq, i, ctx)
324+ end
325+ end
326+ function coordinates! (out, geo:: Point , ctx:: GEOSContext = get_context (geo))
327+ coordinates! (out, geo, 1 , ctx)
328+ end
329+
330+ function has_coord_seq (geo:: AbstractGeometry )
331+ false
332+ end
333+ function has_coord_seq (:: HasCoordSeq )
334+ true
335+ end
336+
312337Base. @kwdef struct IsApprox
313338 atol:: Float64 = 0.0
314339 rtol:: Float64 = sqrt (eps (Float64))
@@ -323,51 +348,78 @@ end
323348function Base. isapprox (geo1:: AbstractGeometry , geo2:: AbstractGeometry ; kw... ):: Bool
324349 compare (IsApprox (;kw... ), geo1, geo2)
325350end
326- function (cmp:: IsApprox )(geo1:: AbstractGeometry , geo2:: AbstractGeometry ):: Bool
327- compare (cmp, geo1, geo2)
328- end
329- function compare (≅ , geo1:: AbstractGeometry , geo2:: AbstractGeometry ):: Bool
351+ function compare (cmp, geo1:: AbstractGeometry , geo2:: AbstractGeometry , ctx= get_context (geo1)):: Bool
330352 (typeof (geo1) === typeof (geo2)) || return false
331- ng1 = ngeom (geo1)
332- ng2 = ngeom (geo2)
333- ng1 == ng2 || return false
334- for i in 1 : ng1
335- (getgeom (geo1, i) ≅ getgeom (geo2, i)) || return false
353+ if (geo1 === geo2) && (cmp === isequal)
354+ return true
336355 end
337- return true
338- end
339- function compare ( ≅ , pt1 :: Point , pt2 :: Point ) :: Bool
340- is3d = GeoInterface . is3d (pt1 )
341- is3d === GeoInterface . is3d (pt2) || return false
342- GeoInterface . getcoord (pt1, 1 ) ≅ GeoInterface . getcoord (pt2, 1 ) || return false
343- GeoInterface . getcoord (pt1, 2 ) ≅ GeoInterface . getcoord (pt2, 2 ) || return false
344- if is3d
345- GeoInterface . getcoord (pt1, 3 ) ≅ GeoInterface . getcoord (pt2, 3 ) || return false
356+ if has_coord_seq (geo1)
357+ return compare_coord_seqs (cmp, geo1, geo2, ctx)
358+ else
359+ ng1 = ngeom (geo1 )
360+ ng2 = ngeom (geo2)
361+ ng1 == ng2 || return false
362+ for i in 1 : ng1
363+ compare (cmp, getgeom (geo1, i), getgeom (geo2, i), ctx) || return false
364+ end
346365 end
347366 return true
348367end
349368
350- function _norm (x,y,z)
351- return sqrt (x^ 2 + y^ 2 + z^ 2 )
369+ npoints (pt:: Point ) = 1
370+ npoints (geo:: HasCoordSeq ) = GeoInterface. ngeom (geo)
371+
372+ function compare_coord_seqs (cmp, geo1, geo2, ctx)
373+ ncoords1 = GeoInterface. ncoord (geo1)
374+ ncoords2 = GeoInterface. ncoord (geo2)
375+ ncoords1 == ncoords2 || return false
376+ ncoords1 == 0 && return true
377+ np1 = npoints (geo1)
378+ np2 = npoints (geo2)
379+ np1 == np2 || return false
380+ coords1 = Vector {Float64} (undef, ncoords1)
381+ coords2 = Vector {Float64} (undef, ncoords1)
382+ for i in 1 : np1
383+ coordinates! (coords1, geo1, i, ctx)
384+ coordinates! (coords2, geo2, i, ctx)
385+ cmp (coords1, coords2) || return false
386+ end
387+ return true
352388end
353389
354- function compare (cmp:: IsApprox , pt1:: Point , pt2:: Point ):: Bool
355- is3d = GeoInterface. is3d (pt1)
356- is3d === GeoInterface. is3d (pt2) || return false
357- x1 = GeoInterface. getcoord (pt1,1 )
358- x2 = GeoInterface. getcoord (pt2,1 )
359- y1 = GeoInterface. getcoord (pt1,2 )
360- y2 = GeoInterface. getcoord (pt2,2 )
361- if is3d
362- z1 = GeoInterface. getcoord (pt1,3 )
363- z2 = GeoInterface. getcoord (pt2,3 )
364- else
365- z1 = 0.0
366- z2 = 0.0
390+ function compare_coord_seqs (cmp:: IsApprox , geo1, geo2, ctx)
391+ ncoords1 = GeoInterface. ncoord (geo1)
392+ ncoords2 = GeoInterface. ncoord (geo2)
393+ ncoords1 == ncoords2 || return false
394+ ncoords1 == 0 && return true
395+ @assert ncoords1 in 2 : 3
396+ ncoords1 == ncoords2 || return false
397+ np1 = npoints (geo1)
398+ np2 = npoints (geo2)
399+ np1 == np2 || return false
400+ coords1 = Vector {Float64} (undef, ncoords1)
401+ coords2 = Vector {Float64} (undef, ncoords1)
402+ # isapprox of two vectors is calculated using their euclidean norms and the norm of their difference
403+ # we compute (the squares) of these norms in an allocation free way
404+ s1 = 0.0
405+ s2 = 0.0
406+ s12 = 0.0
407+ for i in 1 : np1
408+ coordinates! (coords1, geo1, i, ctx)
409+ coordinates! (coords2, geo2, i, ctx)
410+ if ncoords1 == 2
411+ x1,y1 = coords1
412+ x2,y2 = coords2
413+ s12 += (x1 - x2)^ 2 + (y1 - y2)^ 2
414+ else
415+ x1,y1,z1 = coords1
416+ x2,y2,z2 = coords2
417+ s12 += (x1 - x2)^ 2 + (y1 - y2)^ 2 + (z1 - z2)^ 2
418+ end
419+ s1 += sum (abs2, coords1)
420+ s2 += sum (abs2, coords2)
367421 end
368- lhs = _norm (x1 - x2, y1 - y2, z1 - z2)
369- rhs = cmp. atol + cmp. rtol * max (_norm (x1,y1,z2), _norm (x2,y2,z2))
370- return lhs <= rhs
422+ return sqrt (s12) <= cmp. atol + cmp. rtol * sqrt (max (s1, s2))
371423end
372424
373425typesalt (:: Type{GeometryCollection} ) = 0xd1fd7c6403c36e5b
@@ -381,25 +433,31 @@ typesalt(::Type{Point} ) = 0x4b5c101d3843160e
381433typesalt (:: Type{Polygon} ) = 0xa5c895d62ef56723
382434
383435function Base. hash (geo:: AbstractGeometry , h:: UInt ):: UInt
384- salt = typesalt (typeof (geo))
385- h = hash (salt, h)
386- for i in 1 : ngeom (geo)
387- g = getgeom (geo,i)
388- h = hash (g, h)
436+ h = hash (typesalt (typeof (geo)), h)
437+ if has_coord_seq (geo)
438+ return hash_coord_seq (geo, h)
439+ else
440+ for i in 1 : ngeom (geo)
441+ h = hash (getgeom (geo, i), h)
442+ end
389443 end
390444 return h
391445end
392-
393- function Base. hash (pt:: Point , h:: UInt ):: UInt
394- h = hash (getcoord (pt,1 ), h)
395- h = hash (getcoord (pt,2 ), h)
396- if GeoInterface. is3d (pt)
397- h = hash (getcoord (pt,3 ), h)
446+ function hash_coord_seq (geo:: HasCoordSeq , h:: UInt ):: UInt
447+ nc = GeoInterface. ncoord (geo)
448+ if nc == 0
449+ return h
450+ end
451+ buf = Vector {Float64} (undef, nc)
452+ ctx = get_context (geo)
453+ for i in 1 : npoints (geo)
454+ coordinates! (buf, geo, i, ctx)
455+ h = hash (buf, h)
398456 end
399- h = hash (getcoord (pt,2 ), h)
400457 return h
401458end
402459
460+
403461# teach ccall how to get the pointer to pass to libgeos
404462# this way the Julia compiler will track the lifetimes for us
405463Base. unsafe_convert (:: Type{Ptr{Cvoid}} , x:: AbstractGeometry ) = x. ptr
0 commit comments