wkutils/0000755000176200001440000000000014361730142011755 5ustar liggesuserswkutils/NAMESPACE0000644000176200001440000000221314163201260013164 0ustar liggesusers# Generated by roxygen2: do not edit by hand export(coords_linestring_translate_wkb) export(coords_linestring_translate_wkt) export(coords_point_translate_wkb) export(coords_point_translate_wkt) export(coords_polygon_translate_wkb) export(coords_polygon_translate_wkt) export(wk_geometry_type) export(wk_geometry_type_id) export(wkb_coords) export(wkb_debug) export(wkb_draw_lines) export(wkb_draw_points) export(wkb_draw_polypath) export(wkb_feature_ranges) export(wkb_grob) export(wkb_has_missing) export(wkb_is_finite) export(wkb_meta) export(wkb_plot) export(wkb_plot_new) export(wkb_ranges) export(wkb_set_srid) export(wkb_set_z) export(wkb_transform) export(wkb_unnest) export(wkt_coords) export(wkt_debug) export(wkt_draw_lines) export(wkt_draw_points) export(wkt_draw_polypath) export(wkt_feature_ranges) export(wkt_grob) export(wkt_has_missing) export(wkt_is_finite) export(wkt_meta) export(wkt_plot) export(wkt_plot_new) export(wkt_ranges) export(wkt_set_srid) export(wkt_set_z) export(wkt_streamer_debug) export(wkt_streamer_meta) export(wkt_transform) export(wkt_unnest) import(wk) importFrom(Rcpp,sourceCpp) useDynLib(wkutils, .registration = TRUE) wkutils/LICENSE0000644000176200001440000000005614163201260012755 0ustar liggesusersYEAR: 2020 COPYRIGHT HOLDER: Dewey Dunnington wkutils/README.md0000644000176200001440000000516014357667346013262 0ustar liggesusers # wkutils [![R-CMD-check](https://github.com/paleolimbot/wkutils/actions/workflows/R-CMD-check.yaml/badge.svg)](https://github.com/paleolimbot/wkutils/actions/workflows/R-CMD-check.yaml) Whereas the [wk package](https://paleolimbot.github.io/wk/) provides headers and class definitions for well-known geometry formats, this package uses those headers to interrogate and transform these vectors. ## Installation You can install the released version of wkutils from [CRAN](https://CRAN.R-project.org) with: ``` r install.packages("wkutils") ``` And the development version from [GitHub](https://github.com/) with: ``` r # install.packages("remotes") remotes::install_github("paleolimbot/wkutils") ``` ## Example The gist of the functions in this package: ``` r library(wkutils) wkt_coords("POINT (30 10)") #> # A tibble: 1 × 7 #> feature_id part_id ring_id x y z m #> #> 1 1 1 0 30 10 NA NA coords_point_translate_wkt(30, 10) #> [1] "POINT (30 10)" wkt_debug("POINT (30 10)") #> nextFeatureStart(0) #> nextGeometryStart(POINT [1], WKReader::PART_ID_NONE) #> nextCoordinate(POINT [1], WKCoord(x = 30, y = 10), 0) #> nextGeometryEnd(POINT [1], WKReader::PART_ID_NONE) #> nextFeatureEnd(0) wkt_set_srid("POINT (30 10)", 1234) #> [1] "SRID=1234;POINT (30 10)" wkt_set_z("POINT (30 10)", 1234) #> [1] "POINT Z (30 10 1234)" wkt_meta("POINT (30 10)") #> # A tibble: 1 × 7 #> feature_id part_id type_id size srid has_z has_m #> #> 1 1 1 1 1 NA FALSE FALSE wkt_ranges("POINT (30 10)") #> # A tibble: 1 × 8 #> xmin ymin zmin mmin xmax ymax zmax mmax #> #> 1 30 10 Inf Inf 30 10 -Inf -Inf ``` The package also contains plot methods for `wk::wkb()` and `wk::wkt()` vectors. These aren’t intended to be high-performance, but are helpful for debugging geometries. ``` r plot(wk::wkt("LINESTRING (1 2, 4 7)")) plot(wk::wkt("POINT (3 4)"), add = T) ``` Finally, the package contains slightly faster functions to send points, lines, and polygons to a graphics device: ``` r nc_sf <- sf::read_sf(system.file("shape/nc.shp", package = "sf")) nc_wkb <- wk::as_wkb(sf::st_as_binary(nc_sf$geometry)) wkb_plot_new(nc_wkb) wkb_draw_polypath(nc_wkb) ``` wkutils/man/0000755000176200001440000000000014163201260012522 5ustar liggesuserswkutils/man/wkt_set_srid.Rd0000644000176200001440000000271114163201260015513 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/filters.R \name{wkt_set_srid} \alias{wkt_set_srid} \alias{wkb_set_srid} \alias{wkt_set_z} \alias{wkb_set_z} \alias{wkt_transform} \alias{wkb_transform} \title{Modify well-known geometries} \usage{ wkt_set_srid(wkt, srid, precision = 16, trim = TRUE) wkb_set_srid(wkb, srid) wkt_set_z(wkt, z, precision = 16, trim = TRUE) wkb_set_z(wkb, z) wkt_transform(wkt, trans, precision = 16, trim = TRUE) wkb_transform(wkb, trans) } \arguments{ \item{wkt}{A character vector containing well-known text.} \item{srid}{An integer spatial reference identifier with a user-defined meaning. Use \code{NA} to unset this value.} \item{precision}{The rounding precision to use when writing (number of decimal places).} \item{trim}{Trim unnecessary zeroes in the output?} \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} \item{z}{A Z value that will be assigned to every coordinate in each feature. Use \code{NA} to unset this value.} \item{trans}{A 3x3 transformation matrix that will be applied to all coordinates in the input.} } \value{ An unclassed well-known vector with the same type as the input. } \description{ Modify well-known geometries } \examples{ wkt_set_srid("POINT (30 10)", 1234) wkt_set_z("POINT (30 10)", 1234) wkt_transform( "POINT (0 0)", # translation +12 +13 matrix(c(1, 0, 0, 0, 1, 0, 12, 13, 1), ncol = 3) ) } wkutils/man/wkb_draw_points.Rd0000644000176200001440000000401714163201260016207 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/draw.R \name{wkb_draw_points} \alias{wkb_draw_points} \alias{wkt_draw_points} \alias{wkb_draw_lines} \alias{wkt_draw_lines} \alias{wkb_draw_polypath} \alias{wkt_draw_polypath} \alias{wkb_plot_new} \alias{wkt_plot_new} \title{Draw well-known geometries} \usage{ wkb_draw_points(wkb, ...) wkt_draw_points(wkt, ...) wkb_draw_lines(wkb, ...) wkt_draw_lines(wkt, ...) wkb_draw_polypath(wkb, ..., rule = "evenodd") wkt_draw_polypath(wkt, ..., rule = "evenodd") wkb_plot_new( wkb, ..., asp = 1, xlab = "", ylab = "", main = deparse(substitute(wkb)) ) wkt_plot_new( wkt, ..., asp = 1, xlab = "", ylab = "", main = deparse(substitute(wkt)) ) } \arguments{ \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} \item{...}{Passed to \code{\link[graphics:points]{graphics::points()}}, \code{\link[graphics:lines]{graphics::lines()}}, or \code{\link[graphics:polypath]{graphics::polypath()}}} \item{wkt}{A character vector containing well-known text.} \item{rule}{Passed to \code{\link[graphics:polypath]{graphics::polypath()}}} \item{asp, xlab, ylab, main}{Passed to \code{\link[graphics:plot.default]{graphics::plot()}} to initialize a new plot.} } \value{ The input, invisibly } \description{ These functions send well-known geometry vectors to a graphics device using \code{\link[graphics:points]{graphics::points()}}, \code{\link[graphics:lines]{graphics::lines()}}, and \code{\link[graphics:polypath]{graphics::polypath()}}. These are minimal wrappers aimed at developers who need to visualize test data: they do not check geometry type and are unlikely to work with vectorized graphical parameters in \code{...}. Use the \verb{wk*_plot_new()} functions to initialize a plot using the extent of all coordinates in the vector. } \examples{ x <- "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" wkt_plot_new(x) wkt_draw_polypath(x, col = "grey90") wkt_draw_lines(x, col = "red") wkt_draw_points(x) } wkutils/man/wkutils-package.Rd0000644000176200001440000000163514320423165016116 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/wkutils-package.R \docType{package} \name{wkutils-package} \alias{wkutils} \alias{wkutils-package} \title{wkutils: Utilities for Well-Known Geometry Vectors} \description{ Provides extra utilities for well-known formats in the 'wk' package that are outside the scope of that package. Utilities to parse coordinates from data frames, plot well-known geometry vectors, extract meta information from well-known geometry vectors, and calculate bounding boxes are provided. } \seealso{ Useful links: \itemize{ \item \url{https://paleolimbot.github.io/wkutils/} \item \url{https://github.com/paleolimbot/wkutils} \item Report bugs at \url{https://github.com/paleolimbot/wkutils/issues} } } \author{ \strong{Maintainer}: Dewey Dunnington \email{dewey@fishandwhistle.net} (\href{https://orcid.org/0000-0002-9415-4582}{ORCID}) } \keyword{internal} wkutils/man/wkt_grob.Rd0000644000176200001440000000351114163201260014627 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/grob.R \name{wkt_grob} \alias{wkt_grob} \alias{wkb_grob} \title{Generate grid geometries from well-known geometries} \usage{ wkt_grob( wkt, ..., rule = "evenodd", default.units = "native", name = NULL, vp = NULL ) wkb_grob( wkt, ..., rule = "evenodd", default.units = "native", name = NULL, vp = NULL ) } \arguments{ \item{wkt}{A character vector containing well-known text.} \item{...}{Graphical parameters passed to \code{\link[grid:gpar]{grid::gpar()}}. These are recycled along the input. Dynamic dots (e.g., \verb{!!!}) are supported.} \item{rule}{Use "winding" if polygon rings are correctly encoded with a winding direction.} \item{default.units}{Coordinate units, which may be defined by the viewport (see \code{\link[grid:unit]{grid::unit()}}). Defaults to native.} \item{name, vp}{Passed to \code{\link[grid:grid.points]{grid::pointsGrob()}}, \code{\link[grid:grid.lines]{grid::polylineGrob()}}, \code{\link[grid:grid.path]{grid::pathGrob()}}, or \code{\link[grid:grid.grob]{grid::gTree()}} depending on the types of geometries in the input.} } \value{ A \link[grid:grid.grob]{graphical object} } \description{ Using \code{\link[=wkt_meta]{wkt_meta()}} and \code{\link[=wkt_coords]{wkt_coords()}}, these functions create graphical objects using the grid package. Vectors that contain geometries of a single dimension are efficiently packed into a \code{\link[grid:grid.points]{grid::pointsGrob()}}, \code{\link[grid:grid.lines]{grid::polylineGrob()}}, or \code{\link[grid:grid.path]{grid::pathGrob()}}. Vectors with mixed types and nested collections are encoded less efficiently using a \code{\link[grid:grid.grob]{grid::gTree()}}. } \examples{ grid::grid.newpage() grid::grid.draw(wkt_grob("POINT (0.5 0.5)", pch = 16, default.units = "npc")) } wkutils/man/wkt_has_missing.Rd0000644000176200001440000000176414163201260016212 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/finite.R \name{wkt_has_missing} \alias{wkt_has_missing} \alias{wkb_has_missing} \alias{wkt_is_finite} \alias{wkb_is_finite} \title{Test well-known geometries for missing and non-finite coordinates} \usage{ wkt_has_missing(wkt) wkb_has_missing(wkb) wkt_is_finite(wkt) wkb_is_finite(wkb) } \arguments{ \item{wkt}{A character vector containing well-known text.} \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} } \value{ A logical vector with the same length as the input. } \description{ Note that EMTPY geometries are considered finite and non-missing. Use the \code{size} column of \code{\link[=wkt_meta]{wkt_meta()}} to test for empty geometries. } \examples{ wkt_has_missing("POINT (0 1)") wkt_has_missing("POINT (nan nan)") wkt_has_missing("POINT (inf inf)") wkt_is_finite("POINT (0 1)") wkt_is_finite("POINT (nan nan)") wkt_is_finite("POINT (inf inf)") } wkutils/man/wkb_debug.Rd0000644000176200001440000000151714163201260014746 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/debug.R \name{wkb_debug} \alias{wkb_debug} \alias{wkt_debug} \alias{wkt_streamer_debug} \title{Debug well-known geometry} \usage{ wkb_debug(wkb) wkt_debug(wkt) wkt_streamer_debug(wkt) } \arguments{ \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} \item{wkt}{A character vector containing well-known text.} } \value{ The input, invisibly } \description{ Prints the raw calls to the \code{WKBGeometryHandler()}. Useful for writing custom C++ handlers and debugging read problems. } \examples{ wkt_debug("MULTIPOLYGON (((0 0, 10 0, 0 10, 0 0)))") wkt_streamer_debug("MULTIPOLYGON (((0 0, 10 0, 0 10, 0 0)))") wkb_debug( wk::wkt_translate_wkb( "MULTIPOLYGON (((0 0, 10 0, 0 10, 0 0)))" ) ) } wkutils/man/wkb_ranges.Rd0000644000176200001440000000231014163201260015127 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/ranges.R \name{wkb_ranges} \alias{wkb_ranges} \alias{wkt_ranges} \alias{wkb_feature_ranges} \alias{wkt_feature_ranges} \title{Extract ranges information} \usage{ wkb_ranges(wkb, na.rm = FALSE, finite = FALSE) wkt_ranges(wkt, na.rm = FALSE, finite = FALSE) wkb_feature_ranges(wkb, na.rm = FALSE, finite = FALSE) wkt_feature_ranges(wkt, na.rm = FALSE, finite = FALSE) } \arguments{ \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} \item{na.rm}{Pass \code{TRUE} to not consider missing (nan) values} \item{finite}{Pass \code{TRUE} to only consider finite (non-missing, non-infinite) values.} \item{wkt}{A character vector containing well-known text.} } \value{ A data.frame with columns: \itemize{ \item \code{xmin}, \code{ymin}, \code{zmin}, and \code{mmin}: Minimum coordinate values \item \code{xmax}, \code{ymax}, \code{zmax}, and \code{mmax}: Maximum coordinate values } } \description{ This is intended to behave the same as \code{\link[=range]{range()}}, returning the minimum and maximum x, y, z, and m coordinate values. } \examples{ wkt_ranges("POINT (30 10)") } wkutils/man/wkt_unnest.Rd0000644000176200001440000000226714163201260015221 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/unnest.R \name{wkt_unnest} \alias{wkt_unnest} \alias{wkb_unnest} \title{Flatten nested geometry structures} \usage{ wkt_unnest(wkt, keep_empty = FALSE, keep_multi = TRUE, max_depth = 1) wkb_unnest(wkb, keep_empty = FALSE, keep_multi = TRUE, max_depth = 1) } \arguments{ \item{wkt}{A character vector containing well-known text.} \item{keep_empty}{If \code{TRUE}, a GEOMETRYCOLLECTION EMPTY is left as-is rather than collapsing to length 0.} \item{keep_multi}{If \code{TRUE}, MULTI* geometries are not expanded to sub-features.} \item{max_depth}{The maximum recursive GEOMETRYCOLLECTION depth to unnest.} \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} } \value{ An unclassed vector with attribute \code{lengths}, which is an integer vector with the same length as the input denoting the length to which each feature was expanded. } \description{ Flatten nested geometry structures } \examples{ wkt_unnest("GEOMETRYCOLLECTION (POINT (1 2), POINT (3 4))") wkt_unnest("GEOMETRYCOLLECTION EMPTY") wkt_unnest("GEOMETRYCOLLECTION EMPTY", keep_empty = TRUE) } wkutils/man/wkb_coords.Rd0000644000176200001440000000353714163201260015155 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/coords.R \name{wkb_coords} \alias{wkb_coords} \alias{wkt_coords} \title{Extract coordinates from well-known geometries} \usage{ wkb_coords(wkb, sep_na = FALSE) wkt_coords(wkt, sep_na = FALSE) } \arguments{ \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} \item{sep_na}{Use \code{TRUE} to separate geometries and linear rings with a row of \code{NA}s. This is useful for generating output that can be fed directly to \code{\link[graphics:polypath]{graphics::polypath()}} or \code{\link[graphics:lines]{graphics::lines()}} without modification.} \item{wkt}{A character vector containing well-known text.} } \value{ A data.frame with columns: \itemize{ \item \code{feature_id}: The index of the top-level feature \item \code{part_id}: The part identifier, guaranteed to be unique for every simple geometry (including those contained within a multi-geometry or collection) \item \code{ring_id}: The ring identifier, guaranteed to be unique for every ring. \item \code{x}, \code{y}, \code{z}, \code{m}: Coordinaate values (both absence and \code{nan} are recorded as \code{NA}) } } \description{ These functions are optimised for graphics output, which in R require flat coordinate structures. See \code{\link[graphics:points]{graphics::points()}}, \code{\link[graphics:lines]{graphics::lines()}}, and \code{\link[graphics:polypath]{graphics::polypath()}} for how to send these to a graphics device, or \code{\link[grid:grid.points]{grid::pointsGrob()}}, \code{\link[grid:grid.lines]{grid::linesGrob()}}, and \code{\link[grid:grid.path]{grid::pathGrob()}} for how to create graphical objects using this output. } \examples{ text <- c("LINESTRING (0 1, 19 27)", "LINESTRING (-1 -1, 4 10)") wkt_coords(text) wkt_coords(text, sep_na = TRUE) } wkutils/man/figures/0000755000176200001440000000000014357667346014220 5ustar liggesuserswkutils/man/figures/README-wkt-plot-1.png0000644000176200001440000026712114357667346017611 0ustar liggesusersPNG  IHDR4הLiCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i4!O|@IDATx]hW XQfgaR R/'WFPMM&Z_lz#%ɪ&u d ^y7vBRΪvgEX?=nw;Xzy;߹iy^ @ @ @ @@F/Ϩ @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @  @ @ @ @ 4d @ @ @ @  @ @ @ @Cv-Q @ @ @ p&Ggo~vr6e' @ @ @[lM7_e: ]n '~6H @ @ PN;S/鮽r W"@ @ @R^CJt[.x_% @ @ @D '?|"fSWh+@?pٱ] @ @ @gJ|rͷu @ @ @ @CQ @ @ @(@Co @ @ @ @ W\;. @ @ @ Pb7  @ @ @ @@ vF] @ @ @ @ %n @ @ @ @\r팺 @ @ @ @@J|['@ @ @ @ 4u @ @ @ @ 4N @ @ @rhȵ3"@ @ @ @%h(qm @ @ @* АkgE @ @ @J, P: @ @ @U@!Ψ @ @ @X@ͷu @ @ @ @CQ @ @ @(@Co @ @ @ @ W\;. @ @ @ Pb7  @ @ @ @@7Zx'HE~qm @ @ @@05'x7q̙ógV,,,  @ @ @ @rbX%[gf!@ @ @ @hf ⩧*m @ @ @F% 0*#ěַ=\vn @ @ @ 0*Qɖlݧ~z n-x≒v  @ @ @ @` -z3%}GxG:u*]{ @ @ @ @kh%\"pM7ٳg{^|/>_[o?yބ @ @ @\M@jB~7͗\?1bzz:s5> @ @ @ @r~7FU_F?Oq]w=_lƫ^]3@@a @ @/x'g?Y"k^Ҍ @} 4Wx+_9x\yn/ƩSbuu5>G?x}uw[Ϗ~|g @ @J'F#X:&@ 0L-oyK<ˊox{rK}_׎_~kP @ @,=\|s{70? @ܘoh1?w_Ax|CJsJ|+?oo}8-i=WN?uBw|L @ 0lxҲ @h1?w_{K_Rx/|mzkjGŻ9 y}}A{ @ PN(̙3i ~0  @>ss>nwuW<կ~5Ο?+ ^xq/Ș @ @ ŋϯVthx0 @IW'Rıc'Gy$>uae-'@ @ 0N=PZ{#G @/ pvNe7ONz~+ @ @4zOOO'?)S  @LWNL`SmKz׻?裏?/mI @ @%ݴVo)C @ph! jk(> V|gWU/zj… Cze @ @\@Q h4' @Ch%F'055o}[F+ @ @؟|\xqpSZ-j @*^ @ @HN'z4ڊ#G @hU @ @(@ٌ^7t;v$;M @+ pޞF @ @ nAՊJ @ O-eI8zh|H[|k_ @ @ PE.,,DHs @+ 0\ω]|g|ߝ @ @ ŋUXZZ-~O @ x ๕ @ @rt:x衇fȑ#in@ @ojE @ @&LlFjzz:;6a; @ 4 @ @ zZVT*47 @ @`4 q* @ @ PE,,,DHs @F' 0:[+ @ @ 0ŋV4;R> @h^ @ @t:qܹĭ8rH @ 0ZZ @ @1h6OOODZct'&@ 0 7U @ @ 0BMOhZQT܀ @ 4 @ @#(̙3ⅅh4in@ @4 @ @ 0&CT[VciiiL*W& @h~  @ @7 tܹsi8rH @ pp gI @ @d.l6 cǎe^ @L@ @ @؇z;ZVT*47 @ @` @ @P(8sLlaa!F @ p o @ @d&0tAUj52P9 @' P1 @ @/t:qܹV9r$  @8qT @ @2h6LOODZc2L @- P= @ @R n2hZQT܀ @h8<{O&@ @ @3gΤ h @+ pN @ @! ,,,D#@ @ @`:N;w.qss3jZ @ p  @ @ pf3z1;;{x @Wh @ @ 0Q뱻jR @y4U @ @ pEQę3gғh @4  @ @ 0bvTXZZ-O @ 4\ @ @+NΝK5onnFVKs @hȫ!@ @ @`Df3z`阝ѓ,K @0h  @ @؈TcՊJ @' А_OTD @ @|447 @ @ O<* @ @$DVVciiiH+[ @Q 4R @ @@ӉTfj47 @ @ _|{2 @ @nP ^otn'@ @Js @ @8PMlZQT܀ @ @ @(bmm-9??F#  @_@! @ @} _5vwUXZZ .'@ @O @ @P:N5777V @! 0}R% @ @(^opt^.#@ @ ' @ @nH`cc#vZjERIs @G@a|zR @ @^B(X[[KWGHs @K@aZ @ @ l6~[Vcii W @qh. @ @t:܌Z @@L @ @bz1;;?W @ 0n 1 @ @ "hVJ%  @O웪  @ @(bmm-Y8q"F @ 0 ; @ @(@ٌn;pVqқ @ 0) I @ @ P2N;;;iכQ܀ @h @ @@i ?==q @( 0]' @ @.v;jERIs @_@a{h @ @J%PE=8q"F @ 0 G @ @ PfnwjO.m @@m{%@ @ 0N'vvv.Ξ=Z-  @饝 @ @xz}=  @ 4M @ @`nSw_T*47 @ @`&vC @ @`"մ'NDHs @vM@IDAT&O@azjG @ @&NlFZӧ'n6D @^aF @ @@fN'vvvRUWOj47 @ @`2&vE @ @`b Scnnnbf# @\Y@6~C @ @! n*ᄄT*in@ @ 4Lno @ @X Ei'NF @L@d @ @@UnwPZӧO^N @o @ @F,tb{{;=Z @L@  @ @@zޠzc @ pc 7n @ @,6vVmZ155 @C@}K @ @c!PEZO8333in@ @@yk;%@ @ @UnwPgZӧOg_  @@h\J @ @>:Nloojin@ @@o%@ @ @zޠz֪0 @F/ 0zcO @ @ @*ochVSSSin@ @@s;&@ @ @Qj:~x̤ @h(g @ @@6WMtA=j5M! @@{2 @ @ t:NWOj47 @ @  @ @8t3zAz=fgg& @ @C}P @ @ n}ZJs @- P= @ @C("VWWӳ?333in@ @g @ @j[Vcyyk@ @hȻ?#@ @ 0qN'Ӿ666V @  @ @*v^7xf^} @ 0 'U @ @hi/V+܀ @ 4'@ @ @N<nwNZ^ @C@}K @ @.tb{{;=wcc#jZ @ @^J @ @n_cnnr# @h(_ @ @677nZJs @\M@jB~O @ @tcff&  @ p- ע @ @YɓvWX^^{]H @O @ @nXvZgcc#jZ @ @ZUu @ @\U`qq1=. @ @.3 @ @-v;jbjj*  @ h @ @ E+++wǏ47 @ @~+z @ @.8ydtj5/ @؏@~\K @ @%N'Q܀ @# p=j!@ @ @ ,..`^cnn.΀ @+ pr#@ @ @ 677n'VSSSin@ @h^9 @ @(@Q>OL @ @FnDϽ @ @J,pvjw}w5l @a 4 [z @ @J pN;]__Z @Qt? @ @ 4^Gu~ @ 0LajZ @ @@ 677nZJs @ C@a @ @ P(buu5 333in@ @a 4 K: @ @J pɸp`j5  @8 PL @ @c(pNGVKs @ S@a"@ @ 0f3;݄ @J@aT%@ @ 0A[[[nӎZVLMM @- 0lQ @ @0(bee% 333in@ @Q4B՚ @ @&H; . vTVcyyyvg+ @* АkgE @ @ ǃ>*Y__G @F% 0*Y @ @^Gu~ @ p  @ @P`kk+~TyՊ47 @ @(Fkm @ @c*PE033 @@èO @ @` ㎸p jc % @ 0 = @ @L+ѣG܀ @! pʞA @ @`coooPq^& @ػp/NHBl)J; 3)H2Ё)dHHr d-vւŎqtʴ RZKR^6BX.$U\~%.ϙ{=?: @`44 @ @ԱhLNN lmmm)6 @ @J hhX)i @ @sJ6mJYfOfxߘb @Զ @ @:Z|>qR#@ @444 @ @ru]o1Sl@ @а#@ @ P===177WˬX,F  @VS@Cj[ @ @@dd˖-֖b @P& @ @:T*iӦyo|Sl@ @аZ%@ @ P}}}1;;[$ +) @ @ BC @ @-*P.㮻Jxޞb @Է6 @ @U鉹Zb1wb6&@ @| =D @ @ZB`tt4&''S[l @ @`44X @ @ T*شiSZ΋7)6 @ @@=hh] @ @ڊ|>֯_[ @ а0'W @ @h rwuWn @ @^44Nȃ @ @ \mb j  @,^@CA @ @!JRLNNƢ- @' r!@ @ LJ%FFFw^tvv؀ @&vD> @ @A/fggk3X~2bJ @,4 @ @(q]wnhooO @Q@CC= @ @,@OOOf,q/"@ @hhXW @ @ R)hkkK @U@CC @ @@R4y)6 @ @@= hhݑ @ @#Xn]f~#ͭ @XY +m5 @ @+"P.cin!Sl@ @zP;$? @ @!sss;b1[ @ zV @ @ET*d{ll,Rl@ @F$G @ @ T*122>󢳳3 @h S$@ @ ul|>ׯ_].!@ @@ hh= @ @(˱{t 7)6 @ @@# hhhݒ+ @ @C\b! @[@CC} @ @,HT*dvll,Rl@ @Fh;&_ @ @T*122Ξ{ٙb @4F59 @ @G`ݺu1;;[;cÆ |jH @81Ӗu3'?(q}3( qq5Cj @ @,@ݻwunhooO @U@CC\5$?C|O~'=鐙}ߏ??]o;~_EqgԾ& @ @zzzbnnaX?:K @ʉ۰zIwrrફz}CqM7ŷ63ddY7pG!! @ @JRd{E[[w @4޾O>{µ^Ƽ΋ oxCviq=?}{_5 @ @_ T*I{ntvv؀ @.wp ~pqwQ磻;~ 64Ki @ @@M@C |3m۶wx^{~=Oֳ6m~qF$]]]ݓ"{ã>gN @ @ZI =ݻS_}؀ @ vqj^5q饗[)kde 5"p >زe˼skW^ye!kvXfMsSk8cX/~ݟy @ @VIO,qz  @h -KQſΛ@.Jb @ @brr2mmm)6 @ @@hhh\:/[aڵַu޹YľǛz`1ǯkȞ @ @@RT)6 @ @@3 hhh\Zy ͋-'?ɾG=/൯}mu =O7 @ @)066,nkkK @Q`uk36iM~{U=~ ysbzzvWxGO<쑊Y>|q|[ߊ~9dSz& @ @Js=7:;;Sl@ @fЬ;uկᄏ6?R)rwk!e/{!9؇[n?/#Zɣޟ\"@ @"pl|>6lhA @C ޟrJ6[yem޼9/; {Zǥ^Z{Jþ2g?Gҥٓ^ק؀ @ ЬS+T_)6 @ @@3 hhh]^lj'f~G_җҹ^җ\NOLL 7ܐ o[yo~#-v @ @Z'j5yki$O @hhXK~)e˖x8^شiSd8Бr~({MEݏwܹ3N:餸ӥY#@  @ @*066,nkkK @]@CC׼&.y3G? nƸ{E裏O}SvG8j߳6|;ߩ}͕ݟe/{Ydx\~wSl@ @Vj&@ @+T, .$C @а$@ @ brr2ydq[[[  @ ЪZuM @ J%SsNtvv؀ @,w_ @ @*pl-|>  @ POi7B @ 2r9Tu])6 @ @@ hhh @ @@OOO.vUâ @W 3"@ @hZLeq[[[  @ @ BC @ @+(PTbxx8x9Dggg  @ @$ @ @XA/8fggk+_-E @8{%S @ @(qw*hooO @J@Cï, @ @,@ooo(ve] @hd {r'@ @h-[Dwll,Rl@ @44 @ @XrJCCCis9':;;Sl@ @ hh @ @K*p%lm|>K: @ ЌqWD @ P7r9vڕ뢽= @ p` vq @ @\mbk׮]yMB @f;> @ @UزeKLLLƢ- @ pp   @ @T*144?s3 @ ph ) @ @Kbvvvo>ÚM @hU &@ @X6rvJ_wuޞb @x| o  @ @퍹=b1֮]]L @ @ @XB-[Dqll,Rl@ @ а0'W @ @x\JCCCs9':;;Sl@ @ аp+W @ @8%\k|z @ @44' @ @,P.c׮]뮻.Sl@ @а8/W @ @8@ooo>+v^$ @,L@Cœ\E @ @[lyT @ @44, @ @@R}Օb @8< . @ @5K.$fggk|>d @ @` 44,) @ @ZS\.Ǯ]R]w]ttt؀ @_@C۹ @ @z{{cnnP,cڵ-.| @,4 @ @@ lٲ%&&&RťR)Rl@ @G&M @ ЂJ%Sg}vtuu؀ @\@CÑ @ @.䒘U磿K @аV @ @h"rvJ]{ёb @X Kh @ @퍹Zb1֮]"+ @++aeF @ 7tSLLL JRr @ @ hhX:K3 @ @4@RTg]]])6 @ @аf#@ @hRK.$fffjoJE @P  @ @:(˱k׮^)6 @ @аf$@ @h2ޘUU,cڵMVr @ PoOdD @ PG7tSLLLJRr @ @hhXW @ @4@RTg]]])6 @ @а|f&@ @hpK/4fffjUoO @8{%S @ @(˱sδ^)6 @ @аf'@ @hPޘe_,cڵ Z  @ ИsdM @ 7tSLLLJRr @ @ hhX~c+ @ @4@Rg]]])6 @ @а2V!@ @hK/4fffjo̥I @\ @ @#(˱s4õ^)6 @ @аrV"@ @sޘeY,cڵu @ мwoUF @ n)&&&R)r\  @ @`e44 @ @PRP쬳Ί @ @ hhXys+ @ @ԙ^333| Y!@ @@ hhh=W1 @ @>r9vܙ\{ёb @X nU @ @:퍹Z6b1֮]['I @-_ @ @馛bbb"Jr)6 @ @аzV&@ @XEJCCC):+Rl@ @+auN @ J^ziV100JX @$@* @ @4@\;wH @ @ @@oooV-qp#@ @xB>'@ @h*o9&&&RMR)r\  @ @>44>Ȃ @ @`*J :J @ԏ  @ @,^333U| ,'@ @pr#@ @h(r;wL9_s5ёb @/ !@ @X&ޘ^, /\LK @K!a)A @ P7|sLLLKRr @ @@ hh= @ @ T*L3uYՕb @O /"@ @X".,fffjXMC @)a9uM @ r9vؑrk# @ PwodF @ p177WX,ƅ^x3 @VJ@CJI[ @ @`En昘HkJr)6 @ @P#; @ @T*188<묳+ @ PdH @ H.,fffjwX .'@ @а;`} @ @%(˱s45\)6 @ @$K @ @ FZ]], /\.#@ @zPO! @ @#cbb"Q*"˥؀ @G@CCL  @ @!PTbpp0]qYgEWWW  @ @444~ɖ @ @ ]vY>100p+&@ @F$G @ @C عsgk @ @@ hhh=1 @ @cz{{ZŸ s @M@CC|  @ @ |111ΕJr)6 @ @И&k @ @T*188,7  @4fIu @ @ZL\.Ν;S՛7o @ @@c hhh= @ @eZ/qE  @ ЌqWD @hro9SR)r\  @ @444 @ RJ%Sgyftuu؀ @C@CCs* @ @-#peL|>7nlJ @VJV @ @@ عsgbёb @h ͳ*!@ @4@___TZb1.袦Y @hU &@ @4-)R\. @ \k?UC @hJJ7nLyՕb @h> ͷ*"@ @4_333憦+VA @ P @ @rر#yH @4WU @ @F/jB]tQԦ @8 @ @`nOYJr)6 @ @м{2 @ @@C T*ظqc3ό @ @@s hhhU @ @a.򘙙574lQ'@ @ hhX0  @ @VJ\.ǎ;r7o @ @@ hhh=V! @ @Z. qE5\ &@ @#pd~&@ @Xb[n%ӬR)r\  @ @544> @ J%6nܘr=3; @ :ZgUJ @{/˶  @ @444 @ PSSScǎM# @ @+"&@ @XrިȊ@IDATVy B,&$@ @\ @ @@ z1>>*JRl@ @$@* @ @,@R4g)6 @ @p0  @ @X+<|>xN @ @@khhh}V% @ @`;Hnڴ):::Rl@ @%P:>#@ @8lިV B\n$@ @z{b @ @ z1>>)JRl@ @O@C  @ @`QJ%=gqFtww؀ @,D@CB\C @ `+"fffjظqu! @+a @ @G,055wqGgӦMQ(Rl@ @*aR#@ @x\ިVFǽ @ @@ @ @nOJr)6 @ @а- @ @PR@O @ @ hhX  @ @+bffv>~8A @#a1Z%@ @O`jj*t~ӦMQ(Rl@ @G@Cᨹ @ @ FZY#COOÒ @Õs @ @@z1>>$JRr @ @pr#@ @@RpGwww  @ @hh8= @ @ZX+@>P: @,5 @ @;HDPH @8R G*~ @ @@ FZU52d @,4 @ @nR\. @ @`)44,9 @ @-"PTb`` U{Gwww  @ @R hhX*I @ @Z@+Z|>[j% @ @jhhX uk @ @P`jj*㎔H  @ @а"@ @4@oooTZY#C; @ @r hhX.Y @ @H`֭1>>**JRl@ @Z@CR @ dJ%RU~ztww؀ @,P5' @ @ʘUcppS  @ԫzy @ @@`jj*nH  @ @8j&6/ |ߍ_wdSx _^1s)'@ @VH7jm! @ @`%44r<*9ꨣ~Rƍc177-ҹ'?w+8y @ @`niR\. @ @`9rb9uhO}Sё^W-;#^Ν;*J_>;tc @ @/|`` MpGwww  @ @r hhXna8?5fff .7Mut @ P+k̂ d @ @ hhXQ[l1<<||xGU=qO}sdܶmۼ˲WU<#  @ @#+czz6Q.#  @ @0:Fٓ=7ě}O-xgg}}( c5.7WtzhE׳ @4T~M6E @ ZVK׽U'|rjh]F񶷽m-t> @ @@s EZ526w#@ @ʉߢLK^ybN:i-333z }SZU  @ @anZ{ބKRdp @ @oⵏ?y=yϛ/68p^\oAW,_VIsO'?q0b @ xVF,v @ @zP/;`y<{#Hw}Uqtl|Mbccc/~1ݒ磽= @ @n{I7J^9 @ @"pT$"zDkoOxEtIկ~ud w#>Ok_ڽ>1000ﺞxғ4 @X@RN;->+  @ @ hhXVY;~MG~87O<(C=w}wdOfyeM6lwN@ @,N+MS7  @ @ x 7|3/~q۠s?;={Bִp)̐]=c>' @ @ LMMߞ.Bb @ P/zHu)W*xG_j|_/}Ko٣*zŃ^?|; @ @b d Y @ @(wsqI'վ_"o!{ɼg<͂\sM\q} @ pm۶^^9 @ @(wrzw\o{564|cyw5 @ @/=]?xiŚ5kRl@ @M@CCHg>37Ծ8<Ȟ𖷼%O$N>Zu @XUW]ӵ2 -fW @ @Uа 譼G}<Ⱦ @ @ LMMӄ###|bЀ @WqmK @ @@___TJA @]@CC @ @G m۶سgOatt4WN8 @ @@ hh @ @0*JO;XfM  @ @@= hhݑ @ @ꪘ͐=ahhfs+ @XY +m5 @ @LMMZ###Q(Rl@ @]@CC @ @!jvgȐ @ HiJ @XmbϞ=^9 @ @$vK @ @T*ߟ:b͚5)6 @ @"QvJ @ @\uU1==]2{*r  @? '2"@ @Tl߾=;22B! @ @444nɕ @ pV+F,v @ @FШ;'o @ @>۶m={3rA @U@CC  @ @_RD8Sc͚5)6 @ @(wM @ @}ꪘɞ0<<ϧ @ @1444ɚ @ P۷'( )6 @ @*QwN @ @jZz{{ @ @ۨ @ @m{IF>O @hd {r'@ @ZVRDSO5k֤؀ @4FA @ @-)pUWt\.-h @h^ ͻ*#@ @T`jj*oߞ˚ B  @ @@3hhh]T @ R}}}QVk5g Y @ @&vT= @ @@S vmgϞTh @ @f,; @ @*Jlذ!yꩧƚ5kRl@ @I@CC3Z @ @ꪫbzzVc.᦮Wq @ Z{UO @4Tl߾=e53  @ @fl; @ @)ZjA @Y@CC3 @ @bϞ=)6 @ @(wUM @ @@T*ذaCSO5k֤؀ @4fYu @ @M!pWt\.MQ" @ @ hhxb @ JZiJ @ԭ@R 6N9XfM  @ @@ hhhW/ @ PW_}uLOOrr122RyJ @ @ @ SSSnb1<<Bao; @hI -&@ @I/j-! @ @44O  @ @UȞ̰gϞh @ @VЪ;n @ @`*Jlذ!q)Ě5kRl@ @ZY@CC+  @ @UczzC.U @ @44nȅ @h^7B7 @@ @@___TY#úuV# k @ @P[#1 @ @Yo{I华F>O @ O @ @`*J_>x)Ě5kRl@ @R@C @ @ \}1==][1 n) @ 8gdJ @4Tvm( )6 @ @~%WF @ @ejZ[#kdXnݲgr @ yN @4cϞ=))6 @ @ hh!"@ @,@RקyO9XfM  @ @44o  @ @`Iwtm\.###K: @ @@3 hhh]U @ P7SSSm۶P  @ @p`g  @ @K"j6WȰnݺ%$ @ @444 @X5۷Ǟ={Sl@ @\@Cm|B @8lJׯOr)f͚ @ @ph @ @K]zWLOOr1<<|X @Vyu @ @&055۶mK EXL @  @ @E EZS(bݺu @cރ?r;{ZzH ;NU[@ՎΠ9do! v%!ء:bZiUFR.2Z9Tsz>o,do:3}s>y^Cxs @" 4[@ @E믿>򕯤n---) @ @'`\E @xZGy$u{OO @8| oJ @ @!V^=PvM.O~򓇼އ @ @S hxj @ @[{;w?ODkkk @ @ h82/W @ @*P,c߾}g/jE @s' @ @ c||S###Ғr @ 03 3s7 @ Є>*  @ @007V%@ @@\.PsΉ  @ @00wV&@ @\`155ubÆ uޑ  @ @@hR) @ 0{oر#x%Dkkk @ @ h[_ @ @u*P,c߾}Y/:D @ @> 4繩 @Cn!###Ғr @ 0 @ @@ Js9ёr @ 0?. @ @@^:js\lذN*W& @h, u!@ @f pΝ; \rI\@ @̟ @ Pb1*JVŋ+V @h\ {:#@ @@ntH\@ @̯ @ Pr9Reg}vttt\@ @̿7# @ PcW\.^ziU @ |uL @ P*bΝK.$Z[[S. @ @F@¸ە @e˖ERɪYxqHe @ @-`_ @ @nO###Ғr @ p @ @ J}ёr @  @ @ ^:s\\z Tm  @ @ h8 @ @(Js룵5 @ @` 4, @ge˖ERv]xq,_|+ @ tN @ @@C x1>>z  @ @0P  @ @`r >H @ s*!@ @X`155K/-O @s @ PWR)vܙj^~}\@ @Ԗ: @ @s$lٲT*/˗N%@ @fC@l(Z @io1S###Ғr @ P{jLTD @̢@\g) @ @)`6EU @ @, TZ.K/tV  @ 0R @ @ JرcGaښr @ PjlTF @P`ٲeQTU B,_|+ @/ %m @ @`^nO{nݺ5ZZZR. @ @j[@Cm @ @(188<쳣# @ @ 4 @8Bޘrq饗 .'@ @Z@B  @ @Y(Jcǎ룵5 @ @> 49 @8Le˖ERɮ. |üe @ @@- hP  @ 0#o1[n  @ @0P?gR @ @r9K.  @ @0P_Z @ @)z{{cjj*4ƍJo @ @ `NI @ @!JRر#]~hmmM @? wf*&@ @ lٲT*ٻB!/_+ @ @@ hS/ @ 0Mo֭[% @ @> 4繩 @_rbҥёr @ PTN @^72\.7nlz @ @Q 44I @4@T;v/hmmM @o }~'@ @M+lٲT*YB!;F8 @hD xz"@ @ .pM7xr֭Ғr @ P u@ @J\.@yҥёr @ uA @F7~s\lܸiz( @h& tz%@ @u.P*bǎ/8Z[[S. @ @G@C㜥N @ @@ ,[,*JgP;{  @hV z&@ @u&pM7xz֭Ғr @ X w:&@ @5%P.c`` tYgEggg @ @@s hhs5 @5kTVO.M6Lm Y8}?pؙ @\@ @ @@ JE])4c=wyg{׾6g{lE/w]q~ $@ @L8Z஻{7񖷼%N9唔  @ @qbT*B+Vhfu$nX~A?ٟ%Kd OZ @ X<벛[ouo㌌h˓T4 @LছntS[ZZR.hlqgGuPp_O/|!?| @P@C  @ @@ HmuYٙrAc G?5zq{7 q'޽{'7To&U_#Hs=qW5 @ XOĮ]w?qvݕW^'tR?>o @ @@ S- @'R4/hmmzկ~'3s1qeg>3M~o~37Ho @ @a}{/S~ӭկ~ @4hNlx>/zыy{ޓ?7~_  @ @kM\tEښrA |HMvuux&A{{{կ~2=^WdI @ @@ hèR0o?ؽ{R߁qƣ~L>/ iI,zzz{޼V|o۷o^ @%P,Rd Xbš.Y 7 )iP]k@Á{t] @ @ hX3 m~z6ذ ?OGbxx8gMOsU۟41W{=պ?O# @ @`^nK{DKKK/PGS'|rg/~.~ @!cjԉgOk?i_׾6i7cr),xqp@ @@\dqYg"8cxF{޽)ipZLs? @Ԑ'4aS)/{˲߮ٲeK <կ퍋/8,Yw}DG?QWy{^=R+ @ *vژʺrW6h:@u/xA @ @@ T;Rd XbE7çx;ޑ>?_K_R̖9o}Lt? @Ԑ:z.%Ƕm۲2x_Wq;xE @ @|Wedd$ZZZ~69կ쩎3aD_~z<ޟI @ `Zxw{ZYqꩧ+-*!@ @(ߟj;묳3Xxq,Y$5f͚x衇R~Aon[jU @ kO>9>Ν;|fE|>;~ @ Xk׮\.7nlusտ տ׏"^pzt[uhR. @ @0X]|o}[7yZ}wqG @_T4@IDAT*ŵ^~%@[[[-W_}uBNޡL\qٓ{iu @ @ 4y]c=6_}d)b<} @Է@*JDP+VwCuñrʴnu5˗/?`wި>}TZb0sN: @ 5Hڨa;..l//~v) @ @@} |166 >e:088է.T`mۖy_ox׾g?Of9r)ޗ @ @@hhNĿgqq7| $@ @(ߟn833O}yk_{nLMMFϡ^guV5p(% @ @|Da]ug=+n?&F]X @xJkצ)bӦMOy8+2^W?O8XdIu]qww*y @% uuM N;Ї>_n @ @JR?袋m'C x≱bŊOuF_"?x _yko}k<>Z>$@ @0XYWݼ//}K]=o| @ @bT*P(dcMtx+Nj @T 4{x3kAk9 @7cccihiiI @ p#@ @[\.GB833 @ @L 4DϽ @ @֮]SSS@.M65  @ @00ۢ#@ @M P*kM[.R. @ @T@LO @PX,FR:/ r&T2 @ 0R @ @nKDKKK @ @00 @ @M"P./u{gFggg @ @00[!@ @M vژ:riӦ&Z @ @BhXu{ @ @:(Jqצ׭[mmm) @ @fS@ljZ @4@XJuX(bʕ ܭ @ @0а'` @ PrKJGFF% @ @l hmQ @ @(ח:33 @ @\h Uk @ @3LMMerشiSu @ @V 4ɨ @Ԁ@Tk&UnݺhkkK @ 0WJֺ @ @bT*B+Wl@ @ԃz8%5 @ @[bll,<22---) @ @R@\Z @ԩ@\Tg) @ @Z@\ [ @ԡ@uajj*<ŦM % @ @,`OO @ @`JR\s5iuE[[[ @ @00 @ @u$P,Rd XbEUT @ @Q 44I @̂-ccci) @ @K@|Iۇ @Ը@\T%K3 @ @| hOm{ @ @ɬ\.7ojF @4F?a @ @(Jq5פ+׭[mmm) @ @[@|ۏ @Ԡ@XJUV(bŊ5X @ @f0LW @ p[n%'###S. @ @,P' @r%KDggg @ @0аP%@ @5 Y%\.6o\U) @ a @ ФR)P\@ @XH oo @ b1*JVAP+W.`5&@ @L00CF @B[oH  @ @ -`aO @ @`r],Y) @ @jA@C- @ @< T&''s\l޼yw @ @ 4 @ @@CJkR/CCC֖r @ @V 4I @̃@XJT(bʕ- @ @G.`A @R[oTH  @ @$`NC- @ @`r՗,Y) @ @jM@Cz @ @dr.͛7.$@ @̞ٳ @IRW_}umhh(R. @ @ԢZ<5 @ @Y(QT B\rW @ @`n 4̍U  @ @@Mz166j|>r @ @V 4ɨ @P\.G___Zeɒ%ٙr @ @ 4騍 @@?&''r\l޼y @ 0n @ @`^JR\}ihkkK @ PjG @BX,FR, rʣX- @ @0аpv&@ @s"p뭷XZ{xx8| @ @z0PF @ pr9K,  @ @"`^NJ @ @0crr22͛. @ @jO@C흉 @ @Q JӽCCC֖r @ @ 4i @BX,FRɮ( rC\# @ @@m hQ @8,[o5ҵÑS. @ @ԛz;1 @ @'KqՕr @ @ 4㩩 @ ;\..> @ @S@C}  @ @@&P*ꫯN֖r @ @^ 4ɩ @@OOOT*̢P(υ @ 5A @(p뭷hj}xx8| @ @z0Pϧv @hZr}}}38#R. @ @Իz?A @ @@S d{..)4M @4=[ @ @@ JSw֖r @ @ 44) @J'*JsP?, @ uI @ "k׮M G>O @ ($A @ /P.cڵ38#R. @ @4F:M @ @@C dc.. @ uO @u"P*bhkkK @ hDC @ )J%P(ߐ}j @ _@~ ?  @ @@ ڵ+FGGSuÑS. @ @4Fr @ @ 44  @N\.ڵkSgqFtuu\@ @h pz$@ @ɬ\.]vY]կX @ @lh Ek @ @Y۷- @ @@hh' @ԅ@XJZ(ϯI @m -j= @ pvtp  @ @$`N[ @ Pr9֮]  @ @&`N\ @ P199Ֆb˖-5Y @ @%`aC @B`bb"oߞ>  @ @(`O] @ PSb1*JVSP?S  @ @`! 4,=  @ @صkW&) @ @U@C  @ @`r]6]]]) @ @Y@C3  @ @`Abrr2!Ŗ-[ @ @jI@C-Z @ @i&&&bpC[[[ @ @f0O @ "P,Rd{ XjՂaS @ @@ hՓQ @4m) @ @Dh @̣@\5k֤+ @ @_@  @ @`brr21Ŗ-[qw[ @ @G@CJ  @ @&&&bpC[[[ @ @O@ @ @9(QT= BZjN8 @ @ 4驝 @FnTp  @ @ h!#@ @.P.c͚5iJ @ @l @ 0199b˖- @ @(`OUO @ P3qUWz- @ @ h8w  @ @ŨT*ZB!VZ5+Z @ G @ &pmhxx8| @ @O-`m|B @Z\.ǚ5kՕr @ @ 4ǧ @ @crr27e]vT븉 @ ЬM @s&011W]uUZ`` S. @ @xz Oo  @ pDb1*JvOPUV.&@ @0o @En-FGGӊÑS. @ @8< * @ r9֬Y/ @ @#0pf @ @\.[l9u$@ @xz Oo  @ qUW= @ @#0pd^&@ @(QT-ZV:u$@ @8< * @ v[GFF"ϧ\@ @ p @ @ XfMʻNj @ @`ffn @hrr\lٲEO @ h @hBꪫRޞr @ @ h8z;w @ @@ ŨT*¢E .hr @ @fO@YZ @H`1:::|>r @ @ 4 @ Єr9z{{S]]]QE @ 0{fJ @ $199ub˖-Mҹ6  @ @'`aD @ 011W]uUꤿ?S. @ @ h @hbJ%vѢEq4I$@ @̯ @Աݻctt4u022|> @ @00{V"@ @(ۛ:/ @ @007V%@ @ɬ\.[liC @- u!@ @m۶= @ @00V$@ @(QT-Z\pAu @ @@ h3Q @Ԑݻctt4U422|> @ @007V%@ @(ۛ:/ @ @00v @ @:ɬ\.[lNM @? wf*&@ @ym۶= @ @00V'@ @:(QT-Z\pAvl @ @@} hsS5 @̡ݻctt40<<|> @ @00v @ @:(ۛ*  @ @G@8ۅ @D`hh(&''js\lٲN*W& @ @ 44y @f 011۶mK+G{{{ @ @'`aD @5.P,RdU.Z(.Xy @ @0иg3 @8ݻwhcxx8| @ @+`a~F @5(P.7U) @ @̿7# @ԘPLNNfUrkB @ @O@C󝹎  @ @ضm[z/S. @ @X nW @bJ%fѢEqHe @ @4> @ @v`xx8| @ @ '`aL @ (P.7U) @ @,; @,PLNNfr @ @L@TG @ -011W]uU걯/S. @ @Xx * @ @y鉽{f.Z(.yv @ @O'`|N @ %pǞ={ROÑS. @ @  q @ @y(˱zSgggtww\@ @ P;j,TB @s,nݺvrq'@ @8Z G+> @+ضm[/S. @ @- u!@ @9鉽{f/Z(.9ɲ @ @!`a6A @5-pǞ={RÑS. @ @= w&*"@ @Y(ۛV  @ @jS@Cm @ @`֭[>`Z./|V  @ @\ hK]k @ @ LLLĶmR }}}ޞr @ @@ hݳQ @P'ݛhѢ g  @ @K>&D7C='tR%/mۿ{ @8,o=ٓ|>r @ @@m hi/}Kc188|;z7  @ Tr9z{{ǝݝr @ @@ h3 gC ?#\O9˿8Z˧}.o~_]zW7|s<99إ#@ @֭[>`~./Ix @ @ 4luo;Ak>}X,Ɔ cMTQq۶m)?೟l/~җpnq  @M,PZ/ۛXD @ @8>VB \qw;jZۣ>7o38#'+ _B_Jwg{T  @鉽{fV"w6M%@ @4F:y:̰jժxkۿ+VdRo~_ 9Tp=?KoKo}, @aQ]^U;3 @ DuŃ>9/ @ @0Pg7W򕯤=ǸnذaSOhxӛccc7]WT|C??M*xK^]wݴK7)!@ @&&&>]M4O @wϏ]=aaŊ)7?U?usLlٲ%TtҸ;_W L:}d<m^ @QD+T_- /L @h( us??ūC G>YzV|O~x O{?PvM4{طo8UË @@3~:<<|~' @ @@ ʉ:=.ŋdz!_մO=i&{]ZK/0CG5 @uSz{{ӺQ}ʛ @ @@ xBCtC}{hz~ģ>i @ @ԝ#[_WᄄT*)o|c/.+JOwA??I @@ ޽{O[zuJh @ И\gWUi0õ^ n7oi?qUWM{կ~\B @@s |S={#ϧ\@ @ P 祃WOS{O<Ĩ>Yc]u ⮻JKD[[[ @4@uX1tttDwwws! @ GDC /MozSyaޭ>a}ljw^{ޓ @ <֭Kֹ\.i^ @ @&0D=VOU+-{7&կ _Bި~K_ܟI @@ LLLĶmRk׮  @ @GiE's-𶷽-.]wqǴ>E3>|7>}{_|_Gy䠷Uh?$@ @zzzb޽Y͋-uՈb  @ @V ڵ+>яV/^سgOyxx8| @ @%`sλ9bΝq5?mSO=uZ~4Ikkn>:H}'}  @C\.O{CGGGwѽ.  @ @)`9}]{q=PUā7qwm @Wࢋ.|0kc^ā-5y `/"ȨLfh)8:QF mӴER6FQQgP)ķD( pm$;kue~/466F:: @ @@m ̪d7_tvv|͑Y @ M6=cW- @ @ [ z @C3Έvͭ @ @,w  @ PC7n4˗ǜ9sX @ @4~˖ @+bWOT- @ @`4L  @ @`^xalذ!΋ٳg @ @@ (h=1 @*J`ӦMeOc8c# @ @o '@ @ q100PZGccc]vd @ @/a  @ P7n4e˖Ŝ9sX @ @~4˜ @.b5Y @ @@e(h}  @ԝubÆ iw^̞=;5 @ @[@AC}  @ 0-6mt8cX @ @ @ @`8(S @ @T#@ @@ lܸ1zzzҼ-[sIc  @ @ @ @`JڢX,lnn)d @ @!:*  @ P֭ 6$={vk @ @,a @ 06mt֘;wnk @ @*a6 @L@ggg oll  @ @[Pа% @ ƍ;oٲe1gΜ4 @ @ 0\@Ap1 @d.b4nssstttd>  @ @jK@ACml @ Pq֭ 6J^51{4 @ @ 0T @ @@&6mt֘;wnk @ @ؒ-'@ @쌁85 @ @d)!KMc @ @,Xbasss$  @ @%adK @袋bÆ iFɫ&r\k @ @Z@AC֢#@ @@ ƒ%KҬZ[[c޼yiA @ @`24L1  @ PC100Pʨ138 @ @L6> @XP(DWWWAGGG4 @ @ 0Y &Kָ @(L#)hp @ @  Sl @TE](@IDAT}}}ʓWMr4 @ @ 0 &S @RXdI֘7o^k @ @l -l| @T@ggg V @ @R@ATj @U P(+]iGGG4 @ @ 0 B @"(777GR @ @ 0 Z| @`.(&riA @ @`4Ly @ Pdɒt1o޼4 @ @ 0 R\ @`(138 @ @L7/ @*HP(DWWWiA @ @`4L @ PmmmQ,K+knnA @ @`:4L  @ P]tQ+I^5X @ @t(hus @ @Bcɒ%jZ[[c޼yiA @ @`4Ly  @ P100PZIccc$Ogp @ @ @ @i( ՕμtiA @ @`:4L  @ 0mmmQ,K+hnne˖MjLM @ @\@AC @u!pG___kwwwr4 @ @ 0 {O @)ŋq @ @*A@AC%5 @ @` :;;c``4ccccL"@ @ 06 csr @jBP(DWWWҥK#ϧ @ @JPP);a @(cٲeS0) @ @_@AA @⋣/]{wwwr4 @ @ PI *i7 @$088/NGomm;.5 @ @TJ!@ @$\2J#766FOO$bH @ @ (hH @HBk׮M׶tiA @ @4TX @2hkkbX9-["@ @ 09 &ը @/8ҵtwwG.Kc  @ @*Rwƺ @ xt8X @ @@% (hݱ6 @lʕ+c``4BCCClhn%@ @ 0 l @Bk׮M|> @ @*]@AC @ @`mmmQ,Kw655Ųe&0[ @ @L73 @&E/tU\.5 @ @Tj%k$@ @cխqqǥ @ @jPP-;e @ʕ+c``teCCC$Ogp @ @F ոkL @Q B]6=|>5 @ @Tj-k%@ @VڢX,hjje˖mj @ @T#@ @.KM^5X @ @@ (h^ @ ŋ---qqǥ @ @jPPf @"r(444Doo @ @S@ACuU @ @$P(bڵҥK#ϧ @ @jPP;g @xR-bɢ)/_΅ @ @@M(hm @(~KS\. @ @Y@AC5 @ PhѢ48X @ @@ (h~ @R`ʕ100Pʽ!z{{A @ @+vVf @Ԩ@Pkצ-]4|k @ @ r @ @ڢX,rnjj˗U%@ @ ϲ$@ @F֯_}}}i6ݑX @ @@(h @5/088/Nlii秱 @ @ZPPK) @jZ3όR [J @ @4˞ @*( f͚tK.|> @ @jM@AC| @I(ܚb5 @ @lPаYO @T룯/]]wwwr4 @ @ P jqWD @@ ŋ|ZZZbiA @ @V4ʋ @83KCCCD^ @ @ - r @$P(b͚5K.|> @ @jY@AC- @j(bU @ @h @S$~Kg\. @ @j]@AC @:Xxq?~k @ @ r$@ @ @ @<%) - @L@[[[MMMbŊI @ @Y@AC5 @ P5\rI\. @ @ (h( @ @ sXhQ:nKKKiA @ @H #M @ @ SUVEĭt| @ @E r"@ @b B^:]O{{{4 @ @ @`t %@ @@&mmmQ,Kc555Ŋ+2  @ @j]@AC @6K.$"˥ @ @[Pаeg @ 0aXhQzKKKiA @ @4lY @LH`ժU_!z{{'4 @ @ԫzyy @ 0iB!V^|>5 @ @ضm @hkkbX)VX1]L @ @@  @ %\}}}]]]X @ @ (h @ MXhQz]KKKiA @ @4ʕ @تUtMCCCnz'  @ @ز-8C @1  Xzuz}{{{4 @ @ @`| j @*p(sMMMbŊQI @ @4U @آ%\_~yz+r\k @ @ 0~ 7s @RXhQ @ @LL@AE @Unhh^2 @ @d !DC @ PB!V^&dɒiA @ @4LΝ @Թ… X,Os @ @N`VvCzH뮙R$c,, @ZK//~c=O[{キn?l'x, @J8)[ZZNHc  @ @_@AW]uUo;cD?p\wu?gqF$]bE#A @VZ744Dooo5a @ @*Z`fE*NG˗G=j1?#q9甞hi1 @( ztK,|> @ @d#!Ǻ+:xǕskzիJ׍.&@ PaȊbiUMMMqW - @ @@mxDmdqwFgg爹k8C]?KˮMwt͂  @T^_~yԵkF.Kc  @ @PАe͏tiŃ>9cƌ8Sh466I㡇s=7>شiSٹx'" @ P-Inx'pO @ @2ʉAku'q~6֬Y3!h~0 {e%AO~#u @TÑJ-9Rj] @ @jB@ACMl'qחM6т=3D{{k'կA @B d%KD>I @ @$(hZ?yYZC-;1J0s>V*;cś @*M yXX,-)N?J[ @ @9Y5&E`hAC:8`$_Θ1=s΍k&{J!ɫ3~_L_=i[ @`^zi\~k׮\. @ @LqQ7nܘGԅ+V/֬Y~=Dkkk\{n8JO @G>撼zNHc  @ @&O`b+=y1r ҂| @ZH|wVӓ @ @ʉݚZXR0'>14P{vo|뮻ݿj*- @ @ @ @4ߞO(:쾳:+n喲/x "yMBccczj|/ @ @ @ @@(hޮL_+^tb|?L&xի^bƌOi @ @ @ԇL\fM8v[yq'_v~<'g}[>ě: @ @ @ @4f+_xX6f$>:gGrD8sF~]wA @ @ @-7;H^?cwҩ1/Y$9~bL7 @ @ @jJ@ACMm'G___|Yfp=7ގSN9%.u]{  @ @ @ @F4FNe3gΌ+VĕW^/+<Ȣ!e޼yqM7miS @ @ @ @`j4LwMvǗ{oL k+J:ꨚ  @ @ @l]`;~F̘1#^8Ak_R[[n`TC @ @ @ @@% (hݱ2]v%8ҟ @ @ @9- @ @ @~ տ2 @ @ @ @5'TB @ @ @ @4Tʀ @ @ @ԜR  @ @ @ @PP{( @ @ @ Ps jnK%D @ @ @_@AC  @ @ @ @@ (h- @ @ @~ տ2 @ @ @ @5'TB @ @ @ @4Tʀ @ @ @ԜR  @ @ @ @PP{( @ @ @ Ps jnK%D @ @ @_@AC  @ @ @ @@ (h- @ @ @~ տ2 @ @ @ @5'TB @ @ @ @fU 2 @`4+2N=N}ߣ>_~y  @`wu/p23Ӏ u}k[Y:Wu[f,0gΜ-t @ @ @@ *޼ .&6(p'uW*fMo,'ys܊Y @V~oRz|3c}TEwqGi=\.oY @VGvmWtA P1b1nt= .TC@m $ /2f<䱵 #@@ z]JtuuZ!@@ ^:,YRZ_X~}ŭт Pk֭O]  Pg{ D.eH K[nSN9%vu X% @⊸~w޵  P_MozS/Yd%!+I @ @ @ @ xDf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ + YI @ @ @L@ACf"@ @ @ @PАq @ @ @ @4dFi  @ @ @ @ +YY dTwOv[g֬Y Ͽ䏃&p]w\7nB/b>7|xqGnj36 @`>nR+297ǩ 0qn!.Ҹw]s={};o޼9N\ٝ~O񏏸0y/yKJ;$O~'? Oj.#&@@ xBC Ygɻ }?䮾Rnn7|_6|cj0[kܹQ( >ғpgoRЋ/|կ&@\ 0+&38 @ ;UVŧ?gG?HZv7Ʒ;}'O?QOV=QDd"0:ꨲ.xV?x;M\|xv @#/| Xֿ9H% }Μ9sg?Y̚5ke~ @(>׎ <=5yM\y啣&yT5kF=FE/zQ<#Sv_ZKʰK/7c$;$9 @z>eEJx衇JO +>O; 0~/}Ke7ޑgdz8#.ַU  @4TX 8`3 ݸqc$_\8 P^{mYsLYUzUKNs=qo  @fUZ,*GGGG-ٴiS$188Xvc=?p̞=_@z/ɗC׽uCm7?nz~A>ז{ 0ckweU/n,~l T/%@`;o 6ݏ"x]v) @^?!%/yɸ?chAí:1@z^vZTx;ҥΜ93+g} 01arA4K@@ACl N yu]W6`pԫ~s\e}c .;"yΎ;X/ @l|(뮻RɐGqDk @~ӟ|QGŌ36ww StMhѢkJO9#뮻F( e @4TߞY1$6>ٓ-o) @?|0#]b@  A@ ~qꩧ(;} }7n,`v+DܵkFvoק  09!y¯xK^ԧ4_c9fēw4 @`4LX lٲ׿^}+V( @?!ʹݧa"!@|ݺg]w]zk^󚲧5'4 @`|g͚s΍)9Cw1?8ꨣ|Siꫯ7MiA#zJ &q9͞jb;  Poß0ړba,J!@ \}~&@@"p5D#}s1cƌ]~ @@?p$Oz|iWRp 7D?{^BK/}KC?  @J4TFY&#<2?_: Po=PY eX5<A\Qo79{*TC 1o~R̙3cX|ߍ>8xWwÿo8JE*_+VH)x'bq駏7wAzH8xGcnvN;4]H \;^LDHo}'tRk @@v[% D{{{Y!h3k,\w=/]$ @UQ"<&쮻ֻoa~(gb] .`ٞH>0; @gQ bCɭɣ$[z 0ϵ%@D.8[s'? +)~IDAT6{gs~~C/| e/[  @4TX^xa}+CTcK8⪫zVR \ܽ2X`ƍqa(fH>&a7tT/ Oݘ0I @ Z  @ꞷm>_hiiI 0sQ >qvAݓO@+ 7VF \}1f}k_ZС섀Jɣvocnv'4υH|M)4 @@___Xӟ}oYߖC+bĽo}[#z6<).0xq뭷K_b@ )D $H~G)xΜ9o;  @)e```DL /VEyEuGRsΉH368#.vmh6 ?p}g?{ؕ[W=9xӞ6K"sVp"@JE|']{Cʝv)K{qx12 SFm"&*^{E'sύKo~s8(^w\ۈ暲+^Wزϵ[q @z^X| Ixnm{> P *s_Ihoo1>Xre̘1c9 0Ry{^477wܑq4{q-';, @>׎Y / |կ?~sXC@T#Hۛ72)h5*K`ƓY/zej WҗCK b…g@Ժ@R vc}B;殭x{ߛ^ܗHLyI4 P>&JH^,N5k֤l]CGESn4Kfzio_HOj @@ ̬Y&Q;w1!.6 ohjZo|cY~/ _BYߖG}4:묲LD@>׎4C P{'tRYR7tS|/ZaÆbcn~ @`4LXJ]/`fX)$Ak -J\H hZ&B,D[f-\) ܈TH+ڄ-E FŚ̙ygao/|-߿K39A]`: ͛gꫯ?8xpu>AÌX]t  8wܘgڵcϞ=O> '+W\\wo1mv8/^O=x֭[ozӑ^xq陿ڵkL!@?m @x衇믿>ҥK'W{1>q闣GϜ,n{w, @#cǎGu~20~~sXZ'Oy>'~}e8e׭[7mذa|' @`VY i/nŗ_~y8qi @_x[o5se˖g}v8p`|-kի309}{9 Xw\2֬YSZyFpsXzׯ_<WZ5>cGgX+ ]jϸK. hӟA"0q7|oݥgώGyo]ߕ @ ,}7  |ލ%,Qӛw^?<{=1Â\*uR}=n @X|8u8rXbł0_LGf3,ͅ  h pG'O&@/So3gΌ-[m:rǗ_~9ç!@[ x]{k# @;8vؘ^ٳgLG{;ww}wCW,b_9w p |7ܹs?Ø>ظqشix @ @Aয়~|ꫯ?]66l0$|p$ ePs @ @ @ @@.+'rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @!7@ @ @ @ ePs @ @ @ @@. h - @ @ @ @eAC @ @ @ rC  @ @ @ @@Y@P5G @ @ @ @ @ @ P4A @ @ @ @e :iIENDB`wkutils/man/figures/README-wk-draw-1.png0000644000176200001440000073155014357667346017406 0ustar liggesusersPNG  IHDR4הLiCCPkCGColorSpaceGenericRGB8U]hU>+$΃Ԧ5lRфem,lAݝi&3i)>A['!j-P(G 3k~s ,[%,-:t} }-+*&¿ gPG݅ج8"eŲ]A b ;l õWϙ2_E,(ۈ#Zsێ<5)"E6N#ӽEkۃO0}*rUt.iei #]r >cU{t7+ԙg߃xuWB_-%=^ t0uvW9 %/VBW'_tMۓP\>@y0`D i|[` hh)Tj0B#ЪhU# ~yhu fp#1I/I"0! 'Sdd:J5ǖ"sdy#R7wAgdJ7kʕn^:}nWFVst$gj-tԝr_װ_7Z ~V54V }o[G=Nd>-UlaY5V}xg[?k&>srq߀].r_r_qsGjy4k iQܟBZ-<(d=dKO a/zv7]ǰod}sn?TF'|3Nn#I?"mzv~K=گsl<b|_|4>?pߋQrib 2* (Ѧh{28oIyes8';Z9h6g>xRx'b8ՃWOϫ[xn%|^z}%x c8eXIfMM*i4!O|@IDATxwU?X{ED AMH,(cbA1D 1&$ػb>bƊĆ}a{Ͻ>yf=Oy}2#8 @ @ @ @ tMh-B @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@{ @ @ @HN@!X @ @ @  @ @ @ @hH$D @ @ @4 @ @ @ @+ @ @ @ @  @ @ @ @ 9JbA @ @ @ @@ @ @r a֬Y5/ k?zʜ桇 mQf @ PrU @ @§~fϞ]S" s 3f1gΜv @ P[N֮ @ @ @tNS* %@ @ @ @h(O]) @ @ @4 TJ @ @ @# PZR @ @ @ i:M, @ @ @G@$?><MzV_}зo-آ裏BuJa|4O[͊+ZjO|=}f{'ǬY»[w.]UW]nF:aܸq_ N*v޽[ozT @ @@'2R  @ @B/;'V>}Tmn\.:]tp3<rhm:JaΜ9 0`@8~qG1cƄ:f C wqZ>cù\7j5??}-ڣٳ[MnOxt믿jo;p1ǴhA^Źoy晰WN?c9bX'֭[}96pì @ @ rR  @ @&M [mU8⸙3g*: }!=g~SOV2>~h!sυ:(+l=urg5e'x{sNPw]i޹}wzqI'5tr) !Zyam q1Dȳ}  @ @c:ߧ @ @o%|O?{|+(Io馰Vʐկ^[oF.z];=niPkRG 3fNwĭ=m馕->͓/g}v!GqD83!n1|}'(AĒB&g;O @i 4U!@ @u_ꫯ!~Q=Keqܮk?ߥK0lذ/C#OVjR)1H!1a„zS 4v=XnCW\qEZ[;~p9{ @t>.Ҡ}  @$%;'6].{7߬|=}s:ᥗ^ q?oqxwk[uUFmM^y0uym^z_lo:ٳr=ׯ_x׫5Uŭ&V\qwD{f)S}l_+:455e}#^6l>SUN@b-Vdd饗n~EYr/m喕{#n7d>a }N @ @o+Z @ @1dȐy7ɓk? }'4iR裏jUo;z:C\b_WfyA y'ß[nJO>xC YG|@29O{УG]x`+Bo?֧yߖm'n"^l|}l h/{^}vy95uYGrce 7xc}>^7 @ @CE$ @ PO`РAu< zSUtWO>$K8S;g5 ^xafWA{GX|k~zb֑'Oܨv|W!k5X#lՆ6|nΜ9?9&nC,q+>vmp'֝6u]3E믿>F @HS@!ͺX @OBh!e]6k|CQoVZ)zYSkO?K7<923&L0!/Su@ @ 4$+"@ @ o}Vymq<̼s_jqz_ K.d)Z/8ğ kv֭[>)Nr-UC iEfy͋za86`o3}Ccb8!.d2eJOp @ @@h\Z @ Kޖs̩9ߛoYO>/=zm];hhO8S|'Ozs?v-W=]vYx7:瞪{P!7 @p// @ @%s,byx_M#_&WYm'j1[oU30hР0p7wou~qk; 4|' @ P@C1f%@ @ {B֭[S hsǭt 6,rJC3 vy;c^}w^[v+ba-7&LPǬjm$T3mڴ@ @+ б> @XtEsiKaƌեKʗƸo^ 4.9vuךs5?O? <@~C MMM5ۋh5jT={vOXi5kV>: @ @@Z ij @ @@]eY&s=Kot椝q5W9q矇s|A=o\@̙3k~[>\z>{w{?Z[{ @t@C` @ vw>neP(g,1c4iRxiB|E>yke;d5W@ @߀$wD @h$0yz,tlAXguj^Wvپ;h7|3wqG+pwW]kע->y  ߼KZ~;1X1nܸ"̛餓N +Ҽ_̚5+}ѭOѭ-$? ~UM h8 @ @s 4tzY- @vό[+[}꫚_O>=<á{?r˰*\_]z6dk;ǭ*YfSN9%ĐDz.N?O-:ns W^yeC?b$j @ @@ Ʋ @ @@*{WRGqD={vf6|50AT=DÇ^R|C#o!k=%?yX3s1a̙}mF @HS@!ͺX @HF nApQG]τ fmV{7L:5L2%#F|:>+BhBϞ=4hPX|ūU;?{՚ 9{ ]q>hϏHm=?ޜq[BF"nuQ/~Qv @HT@!X @HI# lA%'\yZC=B߾}+ǍWwB-NwXdErvq1̰6|D#еk_'M>=7id'5V^yʿ^;jذaW^;G|:ȑ#Ӂ @iת @ @Xr%C %ğEqKn-,EL_yD [W >ws7rH]q駟S!^[cܹ Z9tuzE#C @ߝ@wg @ @@Ohk=tKTXm:5;nnkf{s[oVYeϸRK-U3"lQRm?ZSs1qM6$8  @ @ -a5 @ @ i/?>t޽]ڰ[|1I|jE 5;jKgv_7bM4/}dv/"O?[o q+<ꫯ^ٺdȑyK @ 4$Z"@ @) ᦛn =X%6^xpW74.N{WO: *K4m٧T3<3c~0z>YtҤIOlO8S‹/ci @ @@'2R  @ @f͚~裏>oƌaUW / e@ٳg{'<7 SN z<%bm _ 8 @ @`hXj @ @ @ @B! PE @ @ @ @K@aᪧ!@ @ @ @ @BQFA @ @ @. @ @ @,M U @ @`0aBx;[o0lذ_ @ @2 4ꮙ @$.pm .W9b @ @@Yl9Qʻn @ @ @ @Cű4 @ @ @ PVVu @ @ @ @^ @ @%޽{ׯ__}^:| @ @*e7GY/u @ @ @ @i r"ͺX @ @ @(@C  @ @ @ @@ iŪ @ @ @ @@J]~O @ @ @hH.VE @ @ @J- Px @ @ @ @Cu* @ @ @ PjR @ @ @ @4ҬU @ @ @ @R 4. @ @ @) Аf] @ @ @Z@w @ @ @ @ M4bU @ @ @ @ .'@ @ @ @i 4Y"@ @ @ @h(u]< @ @ @HS@!ͺX @ @ @(@C  @ @ @ @@ iŪ @ @ @ @@J]~O @ @ @hH.VE @ @ @J- Px @ @ @ @Cu* @ @ @ PjR @ @ @ @4ҬU @ @ @ @R 4. @ @ @) Аf] @ @ @Z@w @ @ @ @ M4bU @ @ @ @ .'@ @ @ @i 4Y"@ @ @ @h(u]< @ @ @HS@!ͺX @ @ @(@C  @ @ @ @@ iŪ @ @ @ @@J]~O @ @ @hH.VE @ @ @J- Px @ @ @ @Cu* @ @ @ PjR @ @ @ @4ҬU @ @ @ @R 4. @ @ @) Аf] @ @ @Z@w @ @ @ @ M4bU @ @ @ @ .'@ @ @ @i 4Y"@ @ @ @h(u]< @ @ @HS@!ͺX @ @ @(@C  @ @ @ @@ iŪ @ @ @ @@J]~O @ @ @hH.VE @ @ @J- Px @ @ @ @Cu* @ @ @ PjR @ @ @ @4ҬU @ @ @ @R 4. @ @ @) Аf] @ @ @Z@w @ @ @ @ M4bU @ @ @ @ .'@ @ @ @i 4Y"@ @ @ @h(u]< @ @ @HS@!ͺX @ @ @(@C  @ @ @ @@ iŪ @ @ @ @@J]~O @ @ @hH.VE @ @ @J- Px @ @ @ @Cu* @ @ @ PjR @ @ @ @4ҬU @ @ @ @R 4. @ @ @) Аf] @ @ @Z@w @ @ @ @ M4bU @ @ @ @ .'@ @ @ @i 4Y"@ @ @ @h(u]< @ @ @HS@!ͺX @ @ @(@C  @ @ @ @@ iŪ @ @ @ @@J]~O @ @ @hH.VE @ @ @J- Px @ @ @ @S˲*Y%\^{o3 @ @ @I ZMMN@-J 5-a_W> @ @ @@GqDD:md6 @ @ @ Q߅%Z ⚚@G l&a%l @ @ @m8qbxg4W@ d 0Ø1c2h$@ @ @ QR.PkD'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @aQ@IDAT @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @hof @ @ @ P@C'@ @ @ @ 473 @ @ @(X@`` @ @ @ @A @ @ @, P0  @ @ @ @ ͌ @ @ @ @ h( @ @ @ @@~fF @ @ @ @ 4 lz @ @ @ @ @C~3# @ @ @ @ 6= @ @ @ _@! @ @ @ @@  @ @ @/ А @ @ @ @`MO @ @ @h?Ĉ2 L4)\{.}ذaa{A @ @ @S@=5&O9yW8f̘0vpa; @ @ @h/[Nd9sf8ák @ @ @hO,\]vYqû[«w @ @ @ P@CQ% lAKܹsKt. @ @ @h(JdN6s=Ww @ @ @  -Zz:G mY8g}@ @ @ @ @FY@)3&7.,2-g͚=ܰ뇿/a̙-ڽ!@ @ @ @ i~x≰F-oʔ) ]tQ/ @ @ @& PMŹ\묳N[M-(uwy'ya5׬蠃ɓ'_~󀗢v+&@A0*@1W"*E( `@0EAŰ߂|=m0w½3sgͭPOz+t-Zpl/KQ%2V@ @ @ @cJY+ׯ﮾j׫W/w뭷Wy.GVN'o;찃;4iӴiӴ*w @ @ @ @(+r֫Wϝwy{sw3fHۘ~͋LV/^ @ @ @ #@ȉ ܜVXuM6M8ѝ{Bdɒ\7> @ @ @  ev*t򗿸ۻ?ܽ .p͛7@ @ @ @BN"v9}z7w\[oE)Sf`k@ @ @ @(;U 7ić8}CG  ϗ_~Yh @ @ @ 8 | J+Zn?%} @ @ @ ry; @ @ @ @y ! @ @ @ @%!< @ @ @ @y |.b@iӦ㏏ZGy2 @ @ @ |@Аo6mڸ#GVh߬s纣>͞=;;Oڅ Fy2 @ @ @ʍrboY;܄ j\xZ=> @ @ @ PdcRe] +zeJ֭[#q@ @ @ @'3ep;vtsq =QL;ݬY!իWCs,@ @ @ @y%!8Y4ٳg  ƍ:Z+ݕTF9}iJ1DZ @ @ @ eL4=)-rnvr^z}S֡ @ @ @*ʽEi믿E |]ӦM]V '\s͔н{wwM7\g{{:wFׯoI!@ @ @ @ h \ /tSLIyy_֭k\:u:}(f*/Ozo Gv뭷^j!@ @ @ @ ,WmI&0h {W)fw׻:}YwWTN:u[ @ @ @  eujd%f8o<3Gn0n9 ôiO?Jn-?Нq˔S@ @ @ @GAC]ӂhƌw)ߤI'qŠ+n^u]w{ݥ^<@תU+K.Ľ{qz2*s%Kxo?xLz鴏s 5k'T  @ @ @*ʾyk cǎ'!<.\s5 ^䡡]vW^qoT? Qq'+Z.Ӹqc7bĈ*cƌq}B  @ @ @ PY4T,XkFaGnvx+{衇r˹T];wvvX_~qǏ@ @ @ @@@Py״ -zwJзoh̪v횰yn5H(f!~a6Q @ @ @ʔ2p>/":df\ztSO=5a[lV[mPJXf @ @ @*ʺk͂ }oFQ>SiӦN'̲BX}Kd[E @ @ @ @HAC^B4iu׍vzk df?lN9sK@ @ @ @KAC^ۼlM7rL۶m:͜93SMPޢEe @ @ @ @ hYlfѾ%f>|x)sw/}7t{Mu7|sBY˖-Y @ @ @ "gZ['/vK(˴+;yVXnܾvDL0!_WFd @ @ @ @6\yEY](jXdk׮3fL{Ƚ<3\3HvuI(c @ @ @*ʺk* <8a #Ν;;} 뫻O?g}u]xN!.[o=w饗") @ @ @ T(+]4:t;0#$r/qܰ*$vAsj@ @X3i/^V[m5W^=YuU]'][y_e@  @ @ kb}i< @Ok׮n„ \n圉 LTp92gyͩEa @ @(E @ Pn~m7~nݺ^QAD(_qJ @ @ Հ& @;~EڶmwE K,q|wwXAj#hѢVBcLVXɻ+O:C"[US[q}>}T Q; @ @(2 E @ PfΜzꩨ}q_m槟~(|0q !J a -#C!?׿?1)AC*DUET-JüW*G.CGQo ǂ @ @OAC_ @eE?7uԩZj}\sjmnoyCC-Mr45\f 3,*M>:W:zDm!@ @(*q @(_|{ٳgI̚WXV[A~cL8uM6l<(d/6!uJUf˪+Er2m*oWzڏW&4AKzRdH QNݺuS~$ylˇire% @zs~9ɞb+r}6 kyXYSWYrJ]V>flUM?G Tez Y^婶mR k\n˶1oTe=m mǶa>>zϱ|X2;Nymj,ܯGӰ,[,zK:|;vtÇwGu1c2dM6q-[-ZtWu3?~ի]W?}ߕ6op .d:!#)- @@ <M4;X-XO?d*63O&vN]6`um}XǶͶ+^y啈_| ҥ:R7u.*IBj!ԙ0aQG9浬C+*6\|TOUnv5hڞO*(eu6V':aTSZ lm 9YTulK.۶l&o*ze_S둏Y&2[,,uaY|Uez @9:В7n\g^HXf7H`uY'޹LP &w믿~u@ | @ |[owy~P4*̿eK1c P vgDuj;6> 8J( A $66c@ڲ:ںo:6>ϩT^6H?]?ض/A2$ 1DG܄нZMZ:a=+Sj嶍R}g- ZYUۥ\Ay:ҀHhY$).L/cjWU'Uyv~=Z0r6ձo@3o??"/>z m5ֈ :ݮ*{GP h;@ dE`Сu5SNjr E_Fl dkZuU^zȶ%2 |Ui:Z+mouGehP_Xf23]G<`|-Kgpe4l0:fxڮuVWV}e間}MBUBaN%ٶH\ky_[Pu(pL9Z4y[_Uy6նcƌqK,^׺uk_cK}YfvJWO^/7|7n tX];+SyX/6,9+߿ɦu֭?9sx lMrU!ࡁ @ X`Nu G:Gա'ث4KKAU#) $[D}uZz4uឪntm^LxA*: ;SLaguT+B/vz+Kr^~Rg[d[7UuΪ\WŋYqA@gyiG0 뒗ú:WERjy ~i>*=jժ{qXQ$ooĢ/Jl 'po`xCzΐXZwqB6x^8swm5`\N$ E' 5N:$c9ss~{j…!:h?Lqv?CА _r@ dM஻r!ݳ:+m˽⁚SGiRd3 H>jR Z)/qHXr[ײ}e婶 k=mkBG5ZG3u]t\GL_F KGKxw}]^ܵ^A~GEz:w {9ӽ2nִiS%k׮N2 nnРA~V)0ѽ\צM@ zճoF.sMwiggymvGuC q={EO'A{oK8j;{Fȳٛov['Mn4I0w\[{ pٮ}ʓ0 BjIp$Ff ;AH&! @@_=Z>蠃 &sk3b f>n龾x9眬bfAa }Yr⒆f!N߽1̚U[iѠfkvX&lAC"e&4 :n̘1n}`B*P=nƮe˖޽ށY6kL  !@b:D(4PPz(kԨ{לYÇ[QI_}xCzMBDžѣG;y=1Č_~yXm~?AFG}yqylP! @ !A @X@^"ëz3kˤ.!p)KäIP,l^2vanZ:[nn-tjnh?dA@fN/PH޽{Pl$͛:(?meH]vѡD+bCFDdu饗ym9ܸq㜼6q^,_XQQRtA {RD6jw +KNZk(Ӥ𚘠Aݓ&OZhaxiH#A @ $аaè<L-G5Sgf#BѶm[Y C!'#%Dz>^fq!J~涶o?+.x!=< xhH5CCzVXz! RBN7cR_l  | D>R<3ٳd@@ @i ԩk^uZ\3텪c=h^KgTه  xhHCAIkkBK9D衡 /v?|oqGbg<v" h`t[rrO zf@bwj&fO8i_ڇM5)\vo"->8/w8M]y};h + ]QymetnrS ѠM 'B!$ҽ,,(b˖-%\BQfs ֞Tۤ 7a7nvm7[! Q'x"ZG @@ZrCi°㖮~n.T,{WO>ٍ9IL p 9O׆3!tq|ˊ? WXڴM6$:? Ϧs5?)W]uk7x]{n}u~W;}t7|p믿?oݙD.=j(ׯ_?wYg>{|x㍽OtW^y{u@=|Y] d"?i{LFa̙cs=.t_c~.iܢp>ڨșN:ɽn֋묐s΍ iРU(;Bu f|0*OGѽW^N"=30tJﲼ= ;adH!X  @5 T~ ;`m[ԩm375{DNCx~wwӦMugz̆=zhH5C ζSnIuq4իW-'?F@`[86Y[Lqy zmK74$^ I8 -G}R<3g{Գr6#U؂UVYŋ$v[ <.<^Y?Ty},( ŗ3ٳgn}ރ'zh;G_O>^2tP/N®m۶#vO@ ^ q&bw .<B+ʓč7ޘVLzmC!+2Y*A kk)$fƍJ\i,H!BN @ bWE=̖Gy(UgBQ\5bb^P/Qsxh(u  b4ljh wjzd}_zh7g_zh@Аx5+hH-Uȳ.\gE|.]܆n_F̐rQTpխ[tUYG3H̢5/K|衇Ν;[o_Svuw(VkL 4h6ez!רQh'wJ7 E(+CNaybyQ0l\W(<<2 j]Ue aVl O*R@ 9/͇ @@&5,f|m*1լ-yhP\V3 ~(CzUR*w`b +<#B`:s6~!34,iz9rg}a~{<X[_T .T E?COEbgƌ'. #1ZJTI^$$|8p袋iC.>ćjuLe{lT,)a{,ZG7?(@!@ I$f"RrۚB U 7HT6IS'bb!zh@АyA]GZq4,Y$qFeL3KzhЀJἌSmS/-hvf{cyn]u\,3gU"n-,瑀n&5o-UyjazR7uﰾCz`# }u̒ *!CO?= ݣ= cذa#qE|p4@ӧ; 5kVSj޼{'} @t?I/1d) E(i>LHbO;ޮA'BQ& h=Sc:ĕzi7 @ ~eU ~:ΝJawXS+yEd K @ȉЪ:PР@;΂<4,ԭ[7ۯOI )q]FI`9*ıiw=;"鹊YDȿ #욭`hL~ (Ԁf4wqǏw&Lp'hԩNB7nmsYq?|ع'zgSN^>CO4ɵk)ԌԖm6{z'70RO!/>CyAb3^̕˻XziwHL/"6 3zh; R@ 4t@ # W˗^ziTՙ+щ`F rCƺ3g5 Oڧfy b§,x<4Wg)YMu^z!k=4h`,beJ$pfXJBf e++c~:_w9y)P( &oZeyxyOP[eȋ~gN<Ą~]P[x=x㍄~ }0@@ @) խu )bkb \~~Ɨuj֠fk7,õ ŹY8H$ ϵI 4Hgޡ!nÁr4"R4V14 9  n5b1=#ĭ/9eokӣ̔)S"nm'7܋T[aB.6T{׍;F|lCTԆ0]8#jȋAνwyZvEE>$FGR;s=\ð) P4!@ P((+ݟr)n7TM@JA&L35F3i|| uF͑tHgpjjggsA8gêub{{{ K"h7kv  KITA9S#7 {PzVxbbwRR =4(dѽD@!L{\a$bE/ S*̏?6gI 3bNBaWZiH ˑl}ucƌ!;:rA ! sA%!מC @`6lXT. F8rD ^{t;\orj֨Q#w]w9uk„ ~fL8;{lw)Hurh {w@jUPx6,Zinh zhw=^+PQowE}&;09sf&HР6^ C͞{9tӅ.ZȲiSIL@"Qp Y(pQ;ZMΝ]UQc@< hu @H PkDBo)vb%x৞z͝;ן +8Z?KI{?)X۶m]=Aӱ<$Ƀ iҠ0 yavM7-]wun޼yV44V‚b^q$?SBA>#EW!BV CƄ!'1- @@Fr[:43B!8쳽ۋb*fl7ް"!6yd !*,PF1mVsK>5\yкN:aÆE3/^{o7~MY A  dn{v64,v׫W/tS\[֭kٲL{F) !4HW16 F"1ykذa\yɓ< ? n/phԨl|=>)\`Sm(c  ꫂꫯڵ#)zrM6u-"|wG aa}Tx}7:DB'[.mv"g}V%>  8@ :^'1bҌM< MiYkʝx^ܰGeګLŠ{Tq?h駟v>;wfj@O\嗀`i@8Iȉj6X_c5j>4CCrBovQ^R dMӐ-ԏV$=N&/or Ʉw n `3H_Ҿ}{-OnSy8Q:DO>K.%|F6J}]U<͖*J  @9N|W_fUlnݺHt5P\;S|"v 'xO ~Bc2P! Yb]ՙiV 1 {rcKm$l<3Rq4P 4Vs 7jWrЊ1wH]0zgݥ^Z倮Įcƌq\rA)d.V~}.yz'5kop3g]&Uga6sӧOwvg\y7`Mj̮JwE٢y督|QG ?wi1 @! 4;CB=<=C\˖-w~ׄqZJmb1"A@3լڌۿ/U(2lvljѣ)l?޵n)VsV[;=d7͓&dgAC80n0^v Kk!Y87|dK;CtbAFW\q;wg+ɃvVc6r+z~<YYk„ ^̡0Er1߿ `1۷L#YYcIY壏>rj'ċx]oZ @ BA::u;SCS"lᆾ(.MLg?F3F4i]ʥȑ#ɓ]82S^.Srqr5[>cǎu:6]OX%`3S؃: 2BBNdΪkFp뇝q4!'֭[҅"^R* tMSAаE=4K?l裏3%.ҥU46{lm!^^{((p $ ;`@$Ƀ@M $`H-O;[ozd}sΉ~ KR0Pm]vI Qy& ˷i5v" P4Tu @Ȋ@(hʉ'Ah /7o;l㒆dz:hTRǗfnO;c ?Yy6P:\4%ע|}ᇮ_~~Ly^DR0yP5Foi^{gSGUf#8"rU_W);PЀ+ȅ~U&PРYq2,Tsl+EN= 8=4ڥU*9-XuY%6,bA'ZQu]N #H\$Ne">swNbdy3xSU-2]_|Z6lЇ:tBh} gvUWw[.Vk}+yyg>}?ڧ Zh>s{DT+̉{]7Wux]ƍMZ#Y(|N*fTACp@@P&A @ Wj^&Ca'4{䡇O>䄎ޫS /X94lL|3U Q7gsBבpAQ%l0`{׽ -C,ͪ;K,DMeO@aG$2 !<ȞTjŽ<CCCAC῿xhX/`Cmf8 x / N?Ǣ 駟aqe}/'a% iakfDj"z mÇw . }gA{Y}!믿}RճM7TcѷB{/vbavm 묉 {D$Ps5m/o{!q{r9)W%hwlѳ= &g@ ;GyĭJx;>p4y.;)Aqΐ38IظϟLlSS~QѣG{ ǪSٸV5\Er[;Ϸ:‹-ҋ;&MDXRP M=2_C̼[Aߙq4 eBA55c9nIQ7kԨecNfzJ5`m㘆;0W*^q-t;wC L>?[=znX^v@t ˻%`Zk@;yVwzAkS o U;z?"/<@$"W8SO=UQ" yِWć~صlSϭ'tkt KX/MJ0W\z]cM4NS'ґGJ={z5;"p ϡVg!Xvgiv[dzVx]nEvSNt Lu}O̺wnيK5( {Xȏ?W8zi%x*Oՙ&y%Ajx]J!<7H РAzжm[5}Q/ҷb_o)Hd3rțٳ|L fKIrkG{zWrݕW^B.XSTcF#TZ<4皆PVQ,hݔ3~K e6&(h{;L' ŧr=,Ln~64xܮ];[U$PX uAv@CCn܄ \޽{>Ĝ<9HP0gX &πp!N:ErTg}Bf^2 H}WcڨkAa'$0!ECT-$'vA=#sӧ 59@T)DBP골$+aC4$`  @eE@.9#B>L9d3lKhB.2m4{|'|⫪cJ< S.6p&U3K3Սzu黠TrڣGcš5jc(h;Cv(4dǩBACUqkzr>4mp5 C%RU &שe0A܆lM>|?cZ4I@bl0d 0ȃ f~ӦMSڗUǬI^gyGby8C xM%D_{ᆄoVV w^M:U^%h޼+O6^:/6z$˅%-$Da]vq c(A/˺ٚZ("YaIN?< P9QfA T6T&w8Ԥr@yWMIpѣ}'fL,0\j@rACzV64m=i~#:s4ڰL3aȉJ4R4(<4ʄjjL4H/w~ܹ˼k؏<) aÆ~@-c4k{ړoݵn:4?s@vۄ SB$&줚 F_~ӗ}@-}a&b;=}}ܐw6C+ ̙=֪U+MBN!Wߑ&MF5% T2eJB; eLxhJ @*1|pɡI@%8J4K>}jv\`*B((6k'͠Pǃf$رw38"c 5".l-آ>?xߡjqz!?,0lt衡<B2 $S'z􉻠!MzhǠSm{Fx/ > YlGo,Uz^ duL>aҤI3yd]MۚUsd ft>@. '')u5o߾>l뭷J$bGW-TBYf%G]{g|~CrXȖn(Eme !&ɦc=ևTOvyg}1uȨwZ+нv&>KHE$رc瑸?RT2 |ui @@P,PဉxX: 3@@3[3M|AwꩧF3aԱաCӵc]XBYK\UWGf9KgV{5ֱV!k jr/hz{grr"΂J.!n!'p GS4b'.^{{w ~yLTOJsƍ!C89 !h~ɺt= e]QC#Qi[ԵkW~Bf`a"tyT( ԩ&!H6ʇP;fΜ^Yѳ!΄0=3Ny lK @ʜfĨh7v\rX١Y2ꈬA@H*/ c8TGⵆqkM޴M4f0Kn+[  U@J* lj,֐a)J4@p緦0… rѢEߎNqԯ_/joNh`Zn5Z򰠁Dy[Ӏ>ʫL3؎%Fh7Xy)7xctz ѷkGd (B>ڷoXᏥC~5^^PCH{"fJCu47+ABp0Q j@<|vs(f.+$VuוsCLQ;q{SO>%C8s3O-q/Dd!"IĄAOAC_cZ@ P4@pf)T4hpGDuȕfiYA"948vV؅АpV5Uֿh{hKh3<4LCACx/)h`zUVN).^}Y?SY `V[m1[F4X4(-sÆ BitRFJ< y{׽K~# Q;O*1qĨ(UGd!Gug'07Kҡf'{`^l;p'j[ow7)w[;CРHȯK/hJ x+)CvڹPb uO?*$8n_cJ(%A+ r1yzL_arru!#g @ P4E/q6cƌ(Q!ɮJ/p첋wie*HRnUJtt.rZ ٱ gmMl hXJJpfX™6㵜3ZJmAh;R9# Mh5Φ]wh08h^K+A<Ë%F]k6)llW:;00_(cHaM0_{˔)SxN>/K Qhz?~/! NCh L-¬Plw^`aN%>H+vuWS/_& iNp!SG&]_C6#4 W_CrT^%O?ƌ\2 Pa4T9 @K@zlM^Չ0k,Z3/=6PꠑR3glR;r=Pvk{ }ޡBNdG;춤V$ZdIT}5ֈq˄&v<47SԊ^RjtMSA͢6,f̹N8~UVn5׌=nA]vt?M.{֥zƅ!=$8J+4ow#{FDJ * Ơw(Օ!Ww<>事+S(yHMy@5vu+9̕t!S}"PUg:~䚼M@ nH>W{fmLR2=GiljN@Co3krwy'ڼTbv!:&p hY!$,w衇S<h/̝;BBCx\'ɛޟY&[SXT߹P s8S@ $Ν;;y(i@H3Qpbť 6@N &NSuLj0e˖N.NۼΜ9~vС[kا,4,AӧO_-|^pGzw ӻwo'э\쪣_ /PaӠXm144!;t!Ugv{V:!&(=!n lh93&hwՠyUm}FmD8"_p5ZJv7Fgh% vaN=xGmQ&ɓ'{'EEZ:q}9{^xoOiM4abL' <7܋[8J-r(#xww375jI{kYNU Gwd׊b_s`( c˞!@ 9d5y.7ѶRP&ƍRS.d5kCJi0o\5k4Lq;o-rA>,JROACkt?oT'R.ӌ!ylt^)!B:S^iPS3HÁ&ͺ7o^rՌagu$f(A8{( ]l"@IDAT!qUf ^y啄Y;S r ^;F-2)?؅=+TC%ݻwӨc*<_ |嗄4M&Qym` /folh<,]B{AP<<#=uzMhݺ W7/zu;ޛC{J=W^:*7|Ə  PY@  }UW]C-hY3,@2G-3fmLtujmQA;9#=ߝcr2 m0ـHH"$9YD l&H&M4 r0}}ڮw4V7;zkRwշ0֜~>w0k  H R Rcj /@9{a9!E reTq"5ށ!Z1j`0?{7 Iy8al Э }Dt)e,#zsEhfNVBQ[։<82sz]c eG^a]_\9u~y4r4@C chZ' Щ%K|b #I.IpV!c=64m묳No }f@,qOI;ntH 5v{~3nN;5蛷#ws>:3Na=3{nVe 6XǑ~r~sj%*M`Nib0 TOh5LӀi4`0 @8PSGuT}e ( 87dNq J'~ԨQt$9XAQhXZj)ώQ/g-wF`e2e;f2dRc[Ihn]w4C|y"X#Hs5W/L0qw/f)2䠀0"7 *8&}%L.u]'Z2̍dʨ=~5b84 䫧ۨ^{Oj~ř<{.qi:yEV_ց{jWt0 aU/䣏>Qzn&i;nBp K <_E7ә'Yg1}t'00!#`P=n8&M kS{@ܰlX;ّN =xNj@q3 +ӀiZ1$j4п ZY~s=׿ʰLӀi B[)q㠌WErbEi") 8051706k<΋Ú @)L0QM׳D}{slbj{H `M ~:g[z]s C=4aYb%eX"߄vͳz崻T¸$wX| i5 zed'O`7 'b8`H3\li8yoF3K 4<Ih s}4E&L@%4@t*iN[EVb׏stoi>0tb !ZM"kz衰Qd_0VMf*o: ,cto'#O,D&<ņG ~%LZ{_^1/ vzs=,ԏ6щ62s3a$dmqz R]Nehyuj{Z )=/7:c㞺4`FFep\nC !8:&N8bJ{ /W8;FsԩSr \el>i PW А&@4`\i4`0 ~ E0Q`}':ĤVhlxj6QCDz!Pss9gP,Caءj8ڡNX f?9=-FTUp,3I<BĀ> [űe{{ '[8Cu;] (wxY[Gg9 ?Y]yݗX䄃g,8y!nzs9ԪlxH;Lpo+㰖WA;8n/r[+`4]hb} c# [+» ,?hu_"ӇӚ1ae"_e}'=&ec0>t㤓N*1Y;bH; 3(J YQcAA x[noXqzx?}0ضt8(e Iz"0N^rkvv@fEhE]#K'x!NHN8ᄠ "P-a]; ksexI `"$>|x( +m0SQH;(Ei1lX|OL?Jmb@Å^Z9^Qp.ݱ+6b~O'5(( i TѼIרL0 @Vͷ~/lqc쓰7pn)JTM_T-*&>b)'RwͭŦӀi4`0 L=ԋqf@i0|0p 6˶PFzovޠ$2HQF[1ԞuBYgҠ%Xw2O7Maj0wԥ-"3P]k!2;(Ju!r]yiC=CYeБIg4P&zAQ;)q4FD@ODnVT.3fԤP, 80'ݖ[nW|9<{w(&e+mP!;=~!-'B"=ɧ~9νmҼ|~)p>Ȧ`mog 9}I |'KiLi ebV_Ӏi4`0 LRjc9GU^{mOuN\xjfN==P]fQ@ݨ0dQFfS*bڗWr^{E-24| o^;,ddJ ɤꫯe{qGb0NB_xlp<~MF} g4 >4J"- _*@9lL祏J Ӏi:VLӀi4`0 jh(yUqt]<|>pD㠂q '7N2}L+;^X?G{&3)HWҪ.>Z') c^cR@:7 XTߍА ϹJY ԟ;guo1c|DƈVgz3n{rUVY3v躬94lя~V_xaYը<5);u}zNY^`^}4`( P g6 LӀi4`(0Jǃ|5F #;ۂ0QZf0)YAV1dt[ωT1Dؑ]g9/3)ϫ\%jp-+={R0mvvs i8\sy5YEK/yGJV4@f6gmɓ} őGI5} sZVйYɳcb *<1c[6pWfUF>:thHhBQ*HҪgq-\8>J+  =dW>a~bM cdžT'.=%t;c=V4`( P h7 LӀi4`(x}%1NcЈYBR(# 0B-đ;2(; P_wT Р^5@C4k2?ӑy1~/S#{QVWp!]D6 /5 D 7kǏ^i>o 7NñN;5E3.w_ryymֳ-it\Ȭz;td; H "}n+P 7s߶0T9眡24h#F1SZ<[.s؄ ￿Z.%tO<wGʌ-b&wy ϸ(VhaXC ŇI0L׀ Ӏi4`0 LT&L#` -!0΢-Ġ 嬆Qzjp 8*mscp74i^\e;lPe 2mf5gnw8∆q Q0]tE`úKBFeẘ˼*i'T8cO?)20!٪|{ :[nd2w6m~_d!0M 8\<~cy h\ B021 ʯ4Z LӀi4`0   P7j_*^ՈXQNr5렂?eMj5!&A+kw=@l2@C# h23@C}Ei)'leu+55@׋ށv3r _ U>k\o> Gu;C^k|zЉx&J^Cme ;K:rwp.B%饗V54|]@ !u10zny `~@j?=rH{ylzM'{:txt mڴi]<sGC|2fx{,ݖMj %hVeӀi4`0 Lkcq|EPy3LjcqD\`\1c8b]3Wyhc bi 6X5,fmb`-x׿5lA/TРC˹+Ǝ6U![*1[G:rT'.eJ] 3f̘JkqF|7bD#l=NԹg'}^tP1@jz گ Jˋ x$42x}!mW_Hْ ^AVhܟAp/yY{x)xFl T*쳏[mo|pTH ŮVp 5ʰ 2*a5zCwx&&<p"hӻz8 )yb" JQA0^7Ds0͐(/AK. jkd@OW4`m( f'd c7xC9"* 0D\tEa[B y衇E#hMbk ž>V;Ӏi4`0 Li0Ad1b``{ホ*JH _0ب>n,bDY߫znUHB2@Cc]U߷׋cc؉݋uN؜^yP띔*P zOGW_}&N;0C֒K.ĉKj.Gt,A鷉=~oFcn׿OAwy !D[ 8y|:wl; |c@AUvaPuYW_zpe]6,lV;VЀ [Ww]wU~?is8 UqsX b8 ; L:=}Y0ePGR Ų 09~~N=Ԇ̔yT i0@C94`0 LӀi7n7xS=#z+iG4'_tPjj ȕEehwyCCjg*r4cRE@fYYzmyjnrs舼VIz}4QJw馛:QU&L;͋}N!4G-ilRNtza |jl0 LӀi5_s=SТ :ˠ% T@0AHNn w%5O6:"A#Un8Q(q &@mljQV_K-|r: \V@Fu\f /d{.VbW8ϙ҂%8hSN^rnWj+wwb;>&qþx7x뭷g'S^7pC˼_W\ѧ(${ڏi4 +jLӀi4`0 䡁>G `?/(w}w\'܌dKʓou0}4@C=K\sg Qk0 Ar Wh0 LӀi5C9G] /P/BϨiub540p'￯c@7.&@-5毂144vRgY }k$>yr,etD" 3r;cv_|ʪw tʵʊcq2Ǐw"jnt+`-c=Y!G'-:3}t^k+O@J#^& _>`#83˔O‰D;cj1~_`#z?nHrLJt@5`b_J׎gyM4ɝ|# Ԝi4`0 L4C(~!Nf ʞ&Pà`@r-3}sK.ݞǀ,C@H$+"M~P4)ԡAmkވȍ#Ɖ3_sLe3Fb@CcvN8Ā04ve.Z>yuR_#CҢ`@ÇZ2,-xuq8۠;cb/_[|ذa5egpfLDž%8 cǚZ:TbŰ4pwBb@CUꫯ*!Z_ ,2gur{2e>ݍ|KrG?SҠױ'qӀi<TjZD @{4m47}t\ȈL;(GnvyӀi4`0  {*׉]fh))ȷ \j̢$ӟ#ʼnc;Q=D9Վ.O?  ^J 8q=\|61!9*i fhPRsG^hw`Bŧ}ؖ4t1!v0 kr vSE@Ih8*ԯ5@*r5Կӿ S,dqueS>0 {C3BO?tM.N]k\q܉⩯CcQ[Ζ@pXV8Т~AZ̿ /B -4(q7Z}SL_& ;CPoE-Nt;Tdǂc ۸q\SDL5~F<8p쳏/1/&:VX=~,#"[ޱ#o l9s)^@ӞN )x7v9wqG` T6ؙb:th˽PߠFao`0 Wh()dAiLcVǒ8H'Ѫ` ᣄÁR܉m,4`0 LjH+Oq.Vkq\4WmP6#{Ũor,Q0@PGV ڎ Y˺.͎n[~ ^y1̡k\1NNi#vxWz^Z&ས; NjmOi>Ufh>2ؾ_98?ڃ ̤u lx" oG4mљpmGHulVE>` &x;(d>?g^1>󩊇 R ;SxfKBp"K h@_SQDY@I;a41Rt i H'-yg;uɞ@ 6$nGp@2wl < 4j\g 0|C=zA1b61 LӀix 3Hrqu 4 `XI+,P@i.첞b61@`5}n:G]l@ Pڡ, eE1~hFu@]ͥhVl ĎТ;6ISg}]%0 9;6h]lh ft%F) {>-n)˙׭QݭI.;|IOw<Э>M?i׏iyWYeϖ3μ zmy)/.`ҤG[7 h4[9;|9%mv2 *Mkmtv55M h`NdP"?v'4MLbj ż.#< fSC;Pti]fA'@.+ʖ>iӦ$ԩS-¦F34`0 L "(IZ3 xne]&l @AnvXj~i@: f?#?}>L"cF?\y , PgQ1,D4∵f}u|+~=0-ɧvqĐ{ ZR-iuv4cg|Xic-ٲix0@CI!k-m c~V >cciLtVHA1|^z)q{@&Ӏi4`0 ^8nj YYz=av(c&[}ti=}hԀ1*m>jFh9cpB]׮ cP#0CKN5"6`xh &@w@) )5q51m馞JC^(ܻy;ڎjx-^ȡ|!@B 4GsB hz/'N 8{.0u>(!UIc R@a@PJ' (2tPճ`O}X0P.)N*` @.0@C.j~!SLȸt\'bC%wynwH_w~x@ ݕjTeLӀi4 AB:rƀshQo@1qzuňsI'ya}q4vPOv4sDO5[.@1<_Rl/̔bÈ#/89=vɪ3&A 0a,C=;a'xrePO*?;01};h1`Ayvc֣9 %Ӑ!CV4lIGƍ@lDbw D E|- ý>~x0`11 4VJߣ~tc!¨Y0<C^ᰎjӀi4`Kϸ4iҤBd kOET4A8w^D0tʺK.OBD} m#Z.R(Z{H3g~W\/1 o6)ȁ>(I4?w#ժ Ό 9*4|hB;b:BVvJ ?;ͰAhLSNk'4iMI8ʹ7N1@q:8]RK-SPnnFTF6s1SN9ryIE3;0͑#G6s3 %%#i%"a[pzR##q#`BR?us3Z^@’҂::+Rk3n8#>E`  / ~z SLҀ LLj@:Ih#4=a_=N: iQ~jʓMvX61 L@>}z"h0+%3ft/OK&/rB%yFxSA"N&dHHP6+mY"轘5@J"R_*)ъ"hAtgUv@:Hʵj5 A$XlAhD藨3WU-by*pnqvL)Z">CP`3'pn"NGɃ'y'B?/A'OHDv29g/,ƉD(U\`F"FdN$*?&D?r9Ҽ kHX/ ĞZ"D 9wY&.h阸7&KHF"$@({-Cр[azJMX'@SxG2a߾**J#rt2 o+5 (ǁ}Q؟u]>*S9f̘O]o9qK8D7>fg.{蒾0SZTN6~䆇" .BuV^ye @IDAT:62CJ@$;;Ihyw=S @;>#+ҟOS Ӓ8q sN]EgmUڎ_4v#yʦɟx9n{ŻYg5ų{>*lM=!ݏi4P< xפ58TitD#:.&Ӏi4`0 tOZ|'tLTaJB݊FYSNu7|ODYFO J"knWAl|cmx#Lz8BР٦8hw'0@C'uqiK9^z$!-3ݔ=ç©矻M6=c>^=2N>}NQ/R~lh<&7չn)0qtkL(&ay InuuBQ#xk1z`+4>#ewl*;P3N p8CL@(ah WAXgKUgӦ9>߫ꁄ뭷wB>\sKP;I+vi'(7o:N DwBp^dtkYIvn<[CX' D*4r_\/zԅ`QGWs@~L9 4HZL\V_}uꪫt\6> !⋎,c饗 뛔CÄCI5s-qʃ>X]`Z`_f67 4tOץ>SLKq,of(}bǰ ۲i4`0 ʬ8E$ߪ7 Иm&mt_SQن_2(7hH_\?+8T@$ tu)8JN?twp˳6eAcљeoo/zØ_(ڿh&FfU= pEM5uQbU evQ9lRYj$==}HN[qҗ<묳|')-D( &koVp.8qbh6HŔ?cKRtC]vYwq."I% }X%y1ĎaR u;_qډzA4cLjRNT99ɿ@SJFۺs9r-萺۠:6l4`0 ʬP>0Uh$W^`$o~SEn'ǻj4?Рj%W-uƎ`Ƃ j_‰qCkI>v`U#uք1@0 iPo]vu޼>69_jsamx@嫂oNyGWۼ4] @QmnʜrB^FLG+@&@}n A-p1=yC|H6 :]ζ^w@Wv;7|ӯO/_Ʉ BpZb@\z%8aF ԅ^;x'2fbl8m4q+ YG_W;oO~6wM_|qlG_b6FNEеJډ?5* 4<ӾoNغ S%΄1/I&5*ڶL]րgFLC.6>/|& h`'ttZ5`]wu~=اz&n44vUahy, { Iq`/]&lCN%׿8G9>(0-([>8x·I裏U3L1TEYDH>MXم;iAѿKwA]PgONAޫr??7Ȩ@Ϥ j 1<8ḵSvm3Qth/a'p#,a:(w:($k[g0 @201 4y99#dccc? oŨ=5e5`tŲi4`0 KbM9H𾎿,`:sDv1b~/㋲11EZNÁyWG ;E"V=vO{w'B~WEyBz̤tWW9N UhR! Gc3F1^7sHOBx' ;VZtEC]٩Kd"IHiP8,Wc>%D0݉2S_k zpW")L a3I<&]03$/`D:_B'~~%L"]]9 Z #P"`D@j5r-6@?pɕW^ U"i4`0 ʤ^{ͳ1@WD3Ƒ≼H8yhĎN8~qQ`(?: i'h[ل~hy_u &N>daSvfgWdD>bfhXs~]w9"bY D9W>=ލYuom1ux܆,s-%Ӌȶu14t |*W](qI`Hag}; ,fsdgrHFD`fBmgn{%q,Vq;D=F~i*o75h _췤2d#/`#m1,-=B  o{W|Cr)ӻP ~W<,CO<-Uӟ35ptJ89Rߒ$ǖK. 3G1'Q܁0=Nz+7`y01 zjX ʢ(O騤E"M=DrPΘ1Í9ҽKXvanNw H |\Ëi4`0 FtpBo Rj2 )IF2V@(ZT6Oks {7nmQ]yy `CΊrT&4ϵΥ8csOGnzBgt~p=vXΐu fmg8:tM5`lZӀi4M D#$ϏN4g !re;\pQ"gA]jDUF[o1p~E$*37oHOQܛenwVɑɳGv`]%8X !D<}~h?|{p0Xc-h0OJDYb;~qX6.B2􋕡[AI*,E8,-nBtnlOʐ~裏~.`*UQzy }Ɏ;8ǽUO$%n?CƚvŪq%z'NDa+@2ݴSVu]C}І*8=B⼮Bqb$aV^Ѝ^ ys$&LH c9<LNx^.?Rv2Bכ|wےzpFB^X׭.ޕhdwO:Dꫯj+)2k@XŁ~=(AQB/yWQU"Ch zow8/n8aߵzTB]+>o FDk%&Unrö `,Ćr<%Br!&l,8õg =Gp˜N@GV_JBj8ww2lذPF>hHN=D؀lKփpm/ fw@B9 01@_"P7ܿ%D4d)T!ޏ}*27Ru^8&P&Ai_O'@E\m;Sոd^8i4Pj `t讄r  %:ͷA"#lTR-H*sބ/ƀۉ0Ktw_H(Neb&#5)N;6I4R"DGozYXFaxH$B3SW޵3=Y}.C؄:2F~yw6D軓}yc`^o03Լff",uGRQ$^ziLJr5 cz87`!y=@* Ca^</z@aI!X[ica %9יc2 kX ybYyڠ&7,MӀhNOi4`hW2xԈjӜqPkBѹ~Ms=n8'P< XaNz,VO}L>Pڲ xL2$>0_}R{=\)O_(4:g۸/ )gt6[iSBHBqBbMN|w8t!ҵrlO>٧Et&<3L=|>lM鋖\rI_oyR.q#ǂC" +p[YSRPaX,|ꫯ^<V `-! C*Pwо=أr]N@.MuT-qCgf+"`wS̘s2קgp Ig5@>8)@ F[o=7J椴6 %%ÜkɖMMiQ3'LzK9[h K1 CB?Y$: 0p qe>$!5d>o0 I8H$ّ3:+<1nnYgm[5 y_uUmC-oDG=H#OP` 8SYDۺnaP@^x@W9q$xINLBmȑ!9" L5p{#5w>Y9U=ܳ{スvG_5=CtD2Dz}|_kz !GA]+<ȭχ&c3>2ƣ&KE{oN]wݍ7ﴁ~`04x#| '8a#@^I1)-e&0_3aڥ/: V?g\~%lVYT `c믿W:Ə$!]o;xf58gMeh+}MCփ@*# ȰbSkp#Fp oq6[ ;z.YcbV>i=ʨT'&KNʱaYӀi x'~Cw)tm}z", z3EmR6nMր1/N$2//f[cEȕ 9EmRB$lX/\zmYy?g} 2ۥ[?묳nI !i&N=Yt"$8O H|λ{)]>?eM'?]&N]M 8[;ye7O3'O{CzHaO";5:r?5r;A'-NFz &܈fBXo cdtر]9Dn[PC"Wo`lPBʵ# z0o>;蠃*0~b ~x8ɗB} SS;~GG$pfT\+-6|pG4-B^lhX߸K]1!рѦٔAwyLfy?.fλy3~[(t9,ݕDM3Ҽ'L=P_|v[74"Pm Qb2|o`2'D8e U:-x0HpʔF5ӑGe#YS’#΃p +Š>\裏|˱sBH?PW_}1cݴiСC[^iiO`407B3L6}vNg1yLҺ0O/w%{/"hHjқi4  JSL$^SNu]z:wYMic v΂1礓N>@娒6V =3jC ^C2i'H7>O V#@#1ܛ*Pw Kds1NC2.:`& > дR(e:.% knyXh|:UVY- l2  4pѷ+ϜJ'zNZk-:Ff·SF YfҼY SRl!KK*s9.j <.Tz6=B? 7K?iИH7GpiHV%9SԸ6U -{UڋT߭ 8R3g,$τp]pJ1>w?/:}Ș}zz]Zii@X<8Jq Ǎ5e{L\-LףoGҀ9Q9uDD`(Wf"k}"5z٨ng0 ʨpџBk! [;Gyă1BQzrHq}[n956,&!<\[{=DyB.sD?5+r4~w_ryl;Fe4%6@IMo*ay |^U 4oUWu@^￿׋_qw/ Mp:4Utd!4t#ge4p+kM*C@3-Bg+ {cY>Lsprn |W=_7jviKZYQO|%}{Ul_|zê>/YZ?[VA4`$4@ 0Uqغ@1 v$R@9)m*-\0g˴@s0@Cszrv]{%YMe־4`0 L@ 0m̤vN40@#BzF3ZW|m+a1+VLi?ׂH:j !O{QfCr;DF㨤c͒﹩dt __ڼ Ka!Cx1l PVMxpkZ=N9DK yzCy8p,VE:Q߫[aqږgyƑNt0l`;K9y}gvL_DҰYHoR׀sgi,%?FC?`Po  ~*cowG+HzEj$D=о Ӏi4`0 t[W{u+s|NO| quq1Px≅ O\08aAVBTb~y C&NN xA胈kn6oי{xˆ Gs޲nV)St[@|;3mk%co;ޅ<ި2X0s '4o;oFaLoaXe]R@C=dG[N| ᝖7y^& 61 'O3.>sYNT嚊 g}sUW9&Jn '@Ŝ]oaJ^ %tR>>ظQ``9?L@KQO{ן{),L11 4tOו:Z}@ f F}ב;H̐.CӦMHvo0 LNj` Fn#Q@`/bgl㪦a9F" ؑڨq^FFe4 `C v*XgK"rN4ᐈjXpp6 =,\&>Ζ@fwq,cp|0->{}X!NAo:ckqq3gh R}㣩svj) h o0 4/[4@$AI6O?tI @v )105eMӀi O h+e2vbLc;&"H>\3'n#Z 9~ qTuu]aR!r^'#Cc /}ؙ1SV"NL_{pp^,-^"TTExvYMeO_J7zLBXc ?+1Ys:l)ڡ +; cYj_Cqƕ9ؽHsa|t([gӾxW|aF<@.Hs8|pmJkD{>Nh)sO֜p Pe0`NFVi-:֌yW]uU_I`iJ[/'Q[ :ԧ$a{D]9LH7C)p ل &511 p `)4` =W,[T#Z^Y 6l3I@D^x1rqFx-LӀi Q7 S_1jL橌H8pHj1# H?O=Q7*:ub9fh$g ,n) 0PČTʹ'G}4Ny7@$6f*IT#s&RL 4pn1iMoF_ޭ1))87mSNs[n+c*) ݻ߀Clw_ ?\ef}I'y01i)YYa>*2HhX~T`j Y ,fe<!,v띺;$6iO8r*X v]_<nƏA}I#G*Ͳtb9PFbk062K*i8MnM7 n֐=_ \Z.b[)s%&o(gbhJ/8x"7L8L'$biKB)isK)i8F}8eӀi4ojF<;%NDr5'֔;4u\;ItB")HU!FD5uv@4 F*5pmF^X T!qm@j[Sa(T8_ڗ\~?mYw8/EnW&D\uVW[6O'j3$^8?6e4Cnw$O>dr7&qa~b0s”~o%7tSƁ:oO$X!GZ;pߟwy*$j8IARC" /2q$"0GZ8֟~BGe٤4@Ti) HgxY.ɟp'x"ciL\0:4:/548k#kYɫZWoSN M(O<JD 9a&Oʥ׮ Efx#<ҵjIp$qAr8xNpn)=Iֵ6vD`Xݹ$A{7 kj}Mg;u$6|DIچF47!#l }W5@CI7kW_ %'NA_#w n l%J' ŭ*_}>*h ڂi4`0 O(͠A;Fw|QƎs D;s:I$'x_lO/|!61_tZCQW^yʼn֑RN"I ,>xqSLq诈IsfTgSN9OOs1N͔)_~)*_m mհ\ ͒h' ,e_ U*\:IE~[E09I}4:Vc{k'N6k![_:ai}rrD3`dhXhDOrI'5>0:(I縗3q~&]Nm~mإiBq3,m[ 'gu'ItYEQRizIЂr9I9'e1;üoTѕ6KE]w!+2|˵st( 5mM@7vj~"j99pǃ)?b*14T[M!|k |ۦs%D"DWMV]uF"q:黐Ӓyq}kvRch+^'/baG\.{G8F 8f1 LҀ jܗ_~|-4Ť (~.h{VeA ,q>8g##~*`AaѺ:8=B__|yDq?䬳Ϊ=P'!{l!q.$0̬Xh8l˽35\.Sƹ +f*1C|T /YKVuJ%~?꺢G-k?m~Mɜ tD|8!|[;IGz W_氘#ĥm\܋ rRBd=B5iN16SPNDߎ}Y'ix:r?|7qDwAÇ3q;VUYf݀>Lwq?NfBԲ0ۛ`4ozc"y0u`+x;V75ҩ:>ǎkvlc0i}@g!^:^ $(|aE}*kRNTضf ;OHiH@tsF4`0 G l|)c+T1A~':a(24lP: a  Ft 0*HƏ0u]s P C/ Z-}hO'M~zvqtIf;t:! )WW%K 2X5)SUkoO @F$~}WL'4a9)3ǀB3.3g?oheK9!,A&Lp͌6@P>q]%WcR_ʻĹ>;' O5pG{8J;AO!JVlշŻ}k=zU~o)J {W_}pZ g3)qu,#!)xWyC=Ow{K \.]r%ziB+om_`. 3|Y,!M94RL6MǷ ~e #4jwPi62Rf ;_sۈ|Ά)*f!`DahD9Fw'"UI|AGѣ 03 JРYyCmEj C z衡MR9wz"bTnj8 ֠BGuDQ^Dx8>uNRČ}{(BnB DnބSDObj8_TJ {~mTG|ob[R 8ŭD g^+Qgu)`3NYJlA5tiZ18hՖZj)Ǐ["NibDxy agD| p2qΑ#G꿥z{KG;!#TR886|s'*T9v3׮ᨄaQ>όP)w`ZCj\[IQȠ"S.,V2;`R'FN=6m5@&8.v}fvchA"5UXefG`==9Q@IDATǎ#`-w'х 0hǫSBK0 CsJh ]C>2Jn8YߏACQȋU|I [e#h-9Q jEi鸳l8C:A!:᫬Jv8G1-RC\0"3Ju@GǥD{2"Z,BC J_N/A!LtIf~)Yc2 AI)y%Fr.KrvI!oDsi&xH"D{/P *9ݙgّQU)0 qW4Z%l7 2⪦{:eC}v7zm Rp_CpbB^s5[: 6 L;8 ճ:s믿D| 3zs^SjA}OYo>?}j) v7J.oAbi:ܸqAqUՐ|@*iD 3C>Fh>L?&|Wqh+6R0󤈶6:DtujUeH4.OD[9y*+'N A%Mq6Ui=qysi4( 6ÐV#/lQ-%4ABhSkePQ҂[O=PQC\2\"R#H7z]_=ޞAb:׷suP#B~̘1W: 7MvmQH4/~j#w狶!mueԠix/曎^ G"8jK9S8ʘV[מ ߙ" j+ζ<Ɖ̷4Y =ZAq#o@-+2BBҤr4kQ Qm@?,%-@^ 5U"e]BGfJ<-ot,h"@%5Hw;]g!Fh ]VshGYBģ>f0xB1 C4(4A?E#$_*Tuvm`'ʀBAT pM7 Uej5'fqF2CAhwr2XMT eNy9bMe0Bv0s.Uh3iQK/ ԇe](U]JGZyTS"`@?*vjDqN8jpγu_gz*~)ݺ#b|!1A*Y387rV),)48Q #E֯)84QjMH P#rX1%#C`PR*.J(õ0ka%3p=Q'*zj(bHUȏ6;"?H}R,dLzpo8yr?V*!K,ZQH Q:-C)exgfm W"t7`Ub-\JxW\Qk[f ]K`!`/rJ& tR-巪!iPhPkCq:vXb'>Hl/T/!&|rɔ;JhDGw8N;5,%ʠX(Lq)W R-" 62I:\DIp qVSO=5Mey1?~8p= _X=h%94_68WI}_jytT3; /0T`>hϲ}!r_8H5^j&˯Ht6EgmwO>芃Np  &L L)bi ߓxΤiSOqtmW%1^K /UrdV r8ٟ)nh 0!^/Ô`N{?A; j`}7TZV55B%[3I=XL/q5S9蠃LH!%% ~^|WH`~~䇦~!·SN9!`!ShPT;%6.sfO#@i[o5E-hS ([՗ 3/2AF=M34a`/IhY%mQ TTKqR 'i<]Ϧn:Gj6hzzy:R)T uC=g(xdkud7H&N }O?tRuppFm;Ƭ;H9FVÓа[2jR#t7K?ELC8SJN"3dzBԗ]vY l^;b<8[lZ a/UVk¿⋃җר}mʽJ1vgVE1!b:RB7ڱzfeC-@Ȗ|7!Ҧ#H?`pG;aH1q@mf4&l宫<^>MzCy{gy|+P1of@}ǷB"PQ|^XC u@ŽOEWvuW'Zl!`@@i 9ֈCVՑDV"PhB!&M OX(Iq#RAlB"%e6tG~FuȖ"[roHQlww2C%y&MG,_~,›wy+ߊ9Bxns DʢB{bІ\ MDbZg&"]nJ_*Mt0(zՌ)$o5{n'|2\v M")4CڦƺE ޫP9}±-Dãkwq!ųqc</v/^m"ӥݞeo.;EݲȆJZ-u QZ7qYcVU#F2*dvvyEҹs̷9 C1r1> rѐYdEB>g$պёfpD"ma Is[ner0 CR  @h сܺyj]?)\-ˍ|:y0w j0hN $z?@~soݍkj(]}ƳAJ"[rmQAWd!58梨ZWDoD{4\,we-ɻ',cN>j[rT3k7r8m8ONԮB˪Z(*vF)XG T%~x?_#w$*%ð6WW&0)h%[BrN;oD#Dn[i˜|}=^ B"F?Uti&!cFT I! 0А'M4lzCAg[n9N;bU$>f!v"4iy.ko '>`G!qתtnG:E$o()رc)E<$z^ GPhA@;"QFv!|H _~9rd (0t ڙ&LR@LLv[^yFmNQBe6mR%")@Pa1@k.NQl; ?A ZCX lXOplAtH_wu_6l0 3T*8y7rĉ .~RGޕ5o}[&vH7Rok K@HNF@)0BC).so* V"?$y]tEÇsV ~:f!`@-Dx@nլ;nt iig|5ztePYc7NAdLjiAfqApcZkJCfҴ6E 4p/3XFDX-%4! j <F.Xyw>WׯQSs5Bu@m IOr;1Rk4F_o~ذ +Vy;: wTHK;58SIEIy5u+|_ Hh @d?N; A"}s="֝vX+d򫮺*,Z:T%9 G gymf!RzG鋨(ժchWoZ=Nַ[c]w80F)%,MQPCs/I<7zZw1ڙf!Fh ?F  \C0 CvXPm.۴39Dv`DؐWnP/Qr2@# J,C{ 4ȡvM7GQ?GqD5uE28f5<u yp׺n~g/]R2ɨd-Τ}Q2w#`)HQYvBpjwZ2춵n=X 2I/xFy睁=c +2H3HZzS8pW6KmQ 2jQC\s56|_ܰ2˄g޾ܹTay)$Q3 u;bB@z!3ό!i< d7i(0} #(ԲTaPvSOӶB8i[QOҶ(c&)1h/c#4*[ C0 !4T3 TՁW'F ea hZh!/q}g>S/^G2ϭp.8r<epcHDۏ3KtD_ҋxN;ԋS {LqP^zKg/^"=D _@.nRyIa-a)Y^^H}y]a?EacAڮ\[ Cί`7Q^RxUq\y(k崹V#}}\qDHB6,3$",nVl|x GϦ8=]^8sڤM7]GHVD^ȼ^Hq_!z!> Q6/K V[mɽDruBi(֪#ۢ6=/JcD=!?>n/Ou26TxIPZ5* L _s-T~ $D<7ӟ;d>1eެ\X y!`@IF/ƼY Ygy&Ch=P63 sO})~'; [nDҠ@L_D5C^<mHQG4 yYgNt2\7J.uTOG{QV?Ct0S&@[jPwչ<ς 65#}E7dCـp*F)H)Dp~0]n ߨN\fi&GNuN_D]צРwD( ]MXj-ovkv[ݎ>IbPP{Q;Ѭ^_jE,`E1\sMH)AZZJX =lUl"dDPgM{89o4PiǾCJoq :0.vPR?֣pQWG(m'OP3N9唸Zq̈́m;(r4K/պ H70^f[6(`|4%(ZbP z0ZC e[!`Vud0T*ZOˀ!ydRBSF3 +;:#Fșs<)K4m.CNdܻi`\Oi%Ԫ묳;vl׆d7RE'+SYj&Hf=SKeuMոyYqIWc "G3z衐@Tj"NFwsoIuq-џ^RK g 4>+ SW)i'^랃dsꩧ6{5{ Ʀؓ(`'+ MrLMi~AjbʆN;m\ |?fHTܒK.vfv!`]Q!`!P@|O81 ghuM\i3m#Gh#=! j-#j~M6Yp3b]nHTlܸqnDUC)QE؃ owqNV\Ѐr<l%q0Pˀv#cpW8+TuAhu F!EҘM=܊|3QMXLh@0lӮ(} NsO=T1p]w9I!H#b T(i/i,U+mlud)LjY~``]~).7."'* 1?e}^zK*CSLѷ*aַDP]vgzꩧ:(]"B6. Qy+ i;VnjNy?@bVZQѨ^pNRą?@rtZ*4$]e^[4 h3C(҉CC773 C@y|Ԭ3d@ÿ+^DzrmʀFX%L%bVd]/N ܅E&ދxHcw C!J^9w{。h y7Es&ܠ 5% zw} y/7L\ZD]y!vz+,Bb3/d-琁ϊmR[Tjuy%%OhsZ%o;lxsL;lfx36@@~'NHY(SŹ~?t*O]x f~{J=\_˖etU.%5^h )3_BFĺr_š^ԽF̐ F}L!%}唯@p?eYe.ϘB?tKDmOZH*ƞtXoB, QiXQFm[}XnI0 D|駟KA%#fB:3C(Fh(ȅj@D'D*VB@6%]/j ^""ڡj4Q*yeD/؋V d~a;gkVȥ 1'r=+Z:89f_#q-zf<&ˍx ozoAEKMpS)*CvaN26l+KH$[| rIb<' ;VHb{;fЈ޸$XK{BQf@^7?_}U> nL!zMuJ[?u䅸BeJ:1škk6 8Z>Ȭyn-i[+)b ,實˷; mQQNr,=&mjQ O?tS-F~a Ƿ`X3e2BذyD91xD u q"v8V_}YDZ@(M6$Fh3?zQ%{=n乍Ow-~l)KDG94}resxD3B믿B[ + ʔJd"YF9MCRބ FmTL2%nR T9眈1~Hy3֍z-p~]J]vY /M}:mA9 VHH5WwioVxj,%3g1+g!8tvt _ I믿(ؿH{7>B id~PZ-2r^$ .QҲWOQ:_k8qȾC*ԣPL{GrF'NIJ8#zX$s#{뭷*THWgVD*/IU $0a%p&b$2 Jwy-"E EK01+iW!`!P4On[ƙwr!iD}.A c=s0k(l:| tx'WhfrDeJ:U+D dzaw 5F5\㈼U{wi ϩY Q#z9Hdv LUd]"\Ĩ]r%!j}H)ڞ0΃yF?嫉'wq}ﯤiw>}Y7ڌDc :w8N^tg@kdEK37[CAl~' +U,@s8ECG~-y;2v6H@5|Qgf#`)'ȶ0 C0JHҰF]IgШ QA.9Q {/ T1XUPiDꫯ^G"n )N:IO,$g5w^zfqJD5j%ӔAP[b%p뮻fm" uwT,SQ'ǏRect ]ˠ-;lرar-7< (as_Cf=5y晃|WxG0@rzRɂ! k$y  2ǐH7nQ$qEPinzZʉ_mmj(צB>i>`'Q%#qN6V\c5(s2 )yu$H+Q9 )Hwh]1\r%3Y-Em!Tk*8RȼgɸI?Dm{QRQ^Ǯ?v$ ȋ:A⏿=-o=GǹBTR, I,b)%%^CqyE/'_ܧ)⤉[+fRNdJ DD@A0BCA.U0t&luf犰}QK U XpXgN%㬫E 6∻R/2 t鹙'6F'V""N:yqf2‰8w ^{yQ&`p>-}%ҩ\<(3^?eJhx衇TURaz."ޛ Ͳ(kƵ3_vN{w޹㾖rr\g'|x|-;SYqCz(Ѷ90w}Y)fr2dĵ=ztG躅 IsYm" Q[Í1'j.qy3Q;_Tb3#4 Q0BCխtt*lt_R  <̠=CǍv0ܓ46:.r +j'R%EA.}2%@#ov(\ ϪqQ|_|Ŭ;I*whJdyWD~AdsEh S<^~ 2ާL3Uvq*~K׉4/BZ{w";r8c5uYv\;P\9rzUu 1>>^3ҳ O>$_)Bl&F{Gt qɢ^ Ѻ5+4edRi(x'W_XdiIO[ #[.8i_Q^qpbΕk x6hL0Ļ<_|@]HI#ݬY}P21BCձ44*jt<= 60UVY%S{W4>Nl%8d9`Qt;S!Si'XLmGY{{=KȐjjI'闺]>ȧM4@z?2oV.Pm-8Fh(m"/Ls9+kG^{Ig_T$oŎyZngPߑ0]z+a*Z^-tf>\Z]O~j[0 =R።o$qeխUHHnV9IfEGvJڙW^yeSN跡Jgg-ߕz$r6xc?qLa3[2 SGדH.ATMߪFS!6Hөu4\_Y 2)O1_OZN Flȑc[TL }/FhvdC O8蠃SH~ϲ ޠq>?4z՛u?@I*,/rG/ΐw{c Jvz_^ci5F %4v, R{Wns뭷>?1kE a#:w_ŭW%2NTyN(C{ZO<1>8ISt{w*9%"6dà#fyM;#[oۓFsC=l馛AcyVn GnҤI~m϶~7e;qDoȱV[ P@y(vIo:%gWs/@Oy+?q2#qf-A8o3B@䔝D"BmW X^wR_-L৶J+QF9qv.rB>vcǎlRK9!8(y߰YIx'ywǺO?}+vBRO0՞'m(IShCmvxr,0>qE"RO3Bq?q|Y61X?klL]ydPƷg אw*Y>>VM~fC{F D\rI,Nj! 骂SOd@d{<|(L%wX-a2Mw:ω*F ?yQp1_rR-‰FUjb}zK[o5"81Q+pᄏ% ^{9Y!NJz' .Urzy~[dMDlo)5ĉ^$x3͚(܅#Ȅ_?잒ECfr;ԴϨga}#8"y `W،!PRz-)VmC0 C0&[0IX"uȔmݏ1JVtQP'2nGU 2ि?ObJQ$Z ?"!D4Pc &7=`3Cl AN:1@U1bĈ#B 黎AJ #<)YňR5X# 6er^{kT"Nh_RQŬ8* U(aԒ̲UɻvYw .T{%zpxy6h2uw^1aWwPBY$N#Ը&(5hέo敨Kx^! 4k \uU] J7Jr Fou҇杤dVe'1Q%~ՄZD%A0%8Sbj!>aQ $t+ {_)iK"AU4Y17*$n%?2)A  :3a@Hã Y)VX!ҞZ~O}ԑ'Mmr{챇[)7u|3rM7dDUW]Aj7beV+[ w;QU+R;>B9& Sh-=Ug-U\~(mVSOP1TSg )9 DTh C=ԓ8Oƅp0?f̘k=nWG'#1VlYg  j "J#:J[9O+r7\/B]\pPW>msNiqk{ >)iw}:e6rN6|}ؖ:1ƒAe"n)ze=ذdB[1tB֛O?[luy'4L;,*4P8<+xVP4""bR# 33C(pB ? CHGȋ#.>h4A7/) 2D+,Ygtt]ܝO#fܷ/r0DovBfPye0ӟs9x ˋ#'sb9%:ˠsʘq?0jQKbdv;i6-Ԏ5Dދ#+^#Iֵc@8HߗAWHX$DTu@ﹿ$ub\3!{QlwQs}>IIB%駟zQsB(k$P;{Iu9jL[B0ܔAly.YY) 2S BFIjG/*)^o{oxzYf/ LEI5=eK s5SZ)&K&MRYquRM$s6BfbQR&Qlj0oV.LA f!`@@攈ڔYm0¥^A=3"ߑH<\Gˌe]_HA-⎨TrWod[/~QÐ؂^@Q:MH\i3T,P\!- Fډ:**1\su{{)Q;Gv~}{,Ef#+]ߦ ړ#G<ht[K,DonrK f_G%1 wyg<?r֓^dSO=Ti PPɲ&Qi{'+^Yy;T UtO&eLH{IwĐ2Z8f{q5"o ޗ>)2P9ۄ SZW/`\i" 63^ ÁciNA#57[n0f{7hK<ㆴƼ+4~@Vs6͚1γRKg1փrCgYS Ph0 C(wuW蜤!JPAjOFCNCZ9眳b& ݃>ح*իBGDY%\҉CcT: ^|Ŋ>Gύ7n\w{0x;pfE6رcà91>g9߬H&r:k&` zQhbllf ڬ0h%b4Ng ^{}nl}D61cKpLbHSXџ%E\'?q{#5}6joROP'k 44U Nϲ w.Ұ~H>r7ʄ(ն!`d~ 3?HѰ\ =Ѕ^&5#a ұH뮻st}i-RL$H PPZh4X裏v#=M \#0x+!`t K9-$8@ΉwrҸ2hsΎ%b+c-op6$d"iO! 3/uzgW YI`YKߢof Qг@O!|$l=c /O^ @XN349zرccyӕ2񖜾#+D7cuH9׼єR} %Hx,-h%cL*)#GxB,m8%V/JIXBi.n{I78C=T#%^tJ2(x4?y C0 \s${ NSV4HQm{IN_|ECSq(30S)t0N(uS=F|63<J.qCK;5Dm'V1"ZHw*0DJAbxBi T&4H&"ʲ,˚0.sIYAl"3jf# ŽǵZ"R}>[瞱vB<иf"/;qԆ_~9d;R m?vlߦ+BHJ 6` _yGB}٧4%4_)*@A5Xn@ݢն %0l榜rʶ"Z@{M6KQ3釡V߿8Yz RԨE 2 `2\e!`@裏B<쫑s{FC#)?aqEtbiK,2/R*ܓ-l v ۘjȗ20_fꪫ6l٤̐geƧ[u'18 U)~L;7`^ίe3qͤM!.5j̆G2vXl7d+B>%v nA4MA?px` }>q&-ҘloG@z0#Qwq{|M7^z)\sRI7.e4)LQ-M7AzvL? !4f^){uE$5R)J }9U+c; 2"`2^u!`x9縹;ւС#ZaMpEVUdJa"Yh$@m:XX`X3<83kQą,>[PAB">W+d1jzp?AҔAAXv _#<"}G{-0q ؐ,D׽$os\??}kƓlz ֿ=+%\ ѱ(xwމEJu65mQf @o\sMGS/~{ Dx6~x* rv}7>s#FJnq# Ǿos jd_ ÓWJ뭷^gZ+;Wz?|5WCT@YvEEェvZyzuTer 7t?zu^;!UА+c2 C(D!*i#h\p~UA"B1L.-آ[yp; g:32PfG%i.h) *ҦQ֝wy{D e֓enLAH里&mPwDl$nLRYmh<R7qӝD)^_JobRWR#2}„ i ;6滄2QR⯼.i DzJGLPNRC@8tY3O<1n&8HRE0-rȐЖ^:[@xNσ2CgH2 %4І!Hj{RkSC ȋ0 .~̛`/D/NL곩Sq>O>d0LzTdmf%2.֓ sx*ڈ,^!7xu"3텠{_>@; ǺO1^tv;M;2`D2/Pe/-I4E*ًԋkA?0$UW, 34 3D9>2ω(EtʷJH^d;8bRry: qO6d38ikc,7c%E!/-O䪬o Ei*Wu(ba(UqM(џp -WoW QcdqQBZu ~Eu"4-^mUEbQ%I[N|kI j?q? {HÓB C0 n D)IS-uMTS?xRyncGR#j9F`/z$!YC~v=zt jiX\X"_<(?mD$rE2,nyu"W]uՠ#̊\Gu꫻w(r[Hzig?=>ScP Ncv(5\!_PiP]S5jTTe6u!5rO?t{"mW\i3C@% = M=uw{'VQ,r=n;B -s1fG>?gxA(œL@ག>F ku>iQ/ 7Ss()X*ʵ1R~SrmtZo~RC4#OӾBVYeXlROB $ +!`A eBCl!e>묳|IC"em,R(, Fص21'X_}Q/R^5qH{Y]^p8H[W^7]+">`⏿+(OZwO1tm# Qzp]Dʷ'i"}m;!晔Z;a#AwuV2.ZuR!34Sć8+++vU(7)߱w߽n-8+[o* Ud2ۮvg^NrK<*(s˼H巭%i*yI' ~V_Xs/K~UIeiOT?WDӦ6y2TZD &RQIM^HSEIRI#^*Sh'd駟՜O Ss/|wC=TL$Y֫BC!~33 :TPj,t:$jEmP2E;$96)7RE75t0 Q,DLGz=Uf# fSHtOMu@PDx* 2[~=9 g`GVD9@O|T%2o໮fs:iof_#0MF89k Crh͆hQZh ZV`cGKS/O7ԗ>S;C奪uɘZWg^C/￿4=q=-ҧJo IuiqEGe$``ԪSnF)4^d&ThTSM%GFړ>;QLA CQ[{lE=Q?O34R B@K-l딪c!c8ww ;Lq):zJ#Ҩ·Ki)ui駟;i:q\l<8u4/S7!veI&w{.z{2L?}[ins\}Ձ:ɹÃ@ /ju, sN#4dZXI 0BC2QD*즛n 4&zre@=\5zWH"J".2q@u6\ P5. $y霡4.EŴ[o+BrXHbO'+P\(2轁C) FtiiBJS"j{~{<ѷfE`vx ]6ј*!m/iw 3}؉zZSRʡ^:"ʑBZ)[zMR&o{o ?.gqHզ~x)9i2MSO=ukIͲ"k׌)C=EqdQn)ə4Tvzawu_q+ SPHsZF3BC7u0 H9 5rvjjeU(2$I$d*ND~; ?pB|h8_2'q[ω4,3`;C‰rCޫ֕[jDЉoWmDr<_Yfɀ°Nh+? nwjI䊣 O"$DUoZx@A\??Ie 7_8q 99dP5"-Nl}&2{ެ?7q̆?މ#''k(ӄcKІɉqwW]uU?裝($2"a |-K9! :!M: 1qQ1N"q%S78hqF$CFw{ Y*nUW ?oyq=a=P'x_&0sL6>u(o ?&$'j%j춺xjI<Ό qGf8uO%IZ0PKY@Y/vugP3lzt8JЬ6%vyՙ޾lK༅(&y8uj 8Khñ%2:cyL JC\MMr(FyI*믿 qfL™ 3hSt[l˘c+):-I3XM:M,DVhrW-2 *M$;3C}%Z1 Dd HNMۋ1r!׏A/nȑ@XNoSy'$1VBB'H 8\s{,vE#^}ޯjww *9?bűU!@{tYfdGQs 䓪j+)V"m1k* d4)CVLC>-߽:+KRμHهHQ'i^–Xc:B>uXznk+? u%= 딀 ٔq2xտ #`_T@RN8y 2H Afq!z"(=HZ y@Q~[L&c @F $09 & "'K$ @F,L sկ3w ]|̽wg_"Ï,$swZD!Γ|1#-N`uD;~`1, ѧE$,``?&``$ֆuYgUf w"FaBN>Hr6,ZrVF*jlcE vYwn NE%zaY#Z-ZI`A3aU rëЬh(PE۞tIv)R+1:֖X'm4z}Qgˍ7hс - mݬqiG( @PcF`_T=>tDi'q$*0ܐ,/p%aY]/Y.z♥ $(BOcG90(dtA hw[R2Zm3ؐ>4e&}i^'5S-/l, LPcsէ'&({FSQgyX&|f0?7mvq 0[ DB# f!8;N鸿`m~g|P{;nǹDz :݇mHykr<`ƽ)opg` QФZS4>s CkQ֊i8 CAX~4 ~6mZ5V2oQ0s N4-|(̽444C qw"``,g*NS<¹ֻ.\@qS暫>nYGnbX>Me9ïo@6 A'/-"l0aeͦ2MJxB`ɋyĉQ/3XeM7Lp0ZP$8>(sV4FYC8ykx!МZke#0Bro]ŵ)~NZ~wO9I֭_[;ב6'BD/M.N4GqĶRS@C+Njȳeb~>G̘1)l 8DG ,~0@ |pD^j>8OϘ (s%Z)OCzUpq_c}xnoر{1ЖtqQG2% ,yB'ERW@yx^r饗*2o *Aku wj9kCAWfm iP;~Dop#iYx.']m\E 9X*(` ҝ^y4PE @C{=9h h h E `#GhW hNa/Hm diB53ezP]ۜ."*CN /F jd>~h#JE.e-8.SO=s~~58:`9PdMt7hhH;8alY?p_3!" h)(ÿOplQ[M1[{3Ɩ3ejYz 5̥٦Nʚo:9-sYf˘9ujAz"8\@(t@IDAT yaI}y+H?eXR<`d`˼V5x1dY|Z3`}Rø}@`倥M#GsnXX)`q)]wމHMXt(&DJ;3 9NB= 礚5J% Z7@H9 X.C܏8[*my>Flf@w00OuѠw-H kO?^uKöET5\$E]=5]{ʰa,s~5P:g8b_}@@@4㍠>oΥ)!G.`u% *L hߨqQ3Əo#/Ө Dapg! CBZN.|P"C|a-N;3ꪫs?>XruNy#0$A,lq6d)_N,wr [`gT8cR>@ =(Gkm9Np.g?w"yʔ);mӓ6m@C`hIW~7;zXE[TQHt9k!Z H?{dcc v#8{|G,snUn"W(RxJ{ߋ/:?6q4 <%ZگGe=I%|nKEӨ~^{fzڀb}ZfbOveY,`SI X(@R==-\aCh,PktټGԊ^'J+@z=6lkv&9 jԹΥh$C/u TRb 44P G?2  Jb4+o:$jB9ˆr3ԀB]ʢb/ëh#k@k,s#F"]3IimrوU#bV#QF FFFu#,##/#T#c%Ј>RZ@u,4E"Ks[nTP9;?1ZRPQ&qVwBY^}֩;6WaB1'g$4.&Pt-W{aD'|rj9ŘVz!W` w4 V9XVQMRp߯]Vʦs;Pc)5 bSAZߊymSN9%*.K)HOaDZ,lJ>zMO[~]I]@ aI9koՈ-ŋ$R)B +['M;A 0]wvXdEl:b=怬HъM#@H!/M[{8#a)>̿/%\bo>KOU"a_l&{[lH:3`K(I랷0]w֋4- ΥxRmЀh*xen qɗ(c2ˮ!DsJ鏕hFIs`&1P}6l8{E?rrjd+0 Bry>Bt VЭ[O@戄V c$sU]4㏻+X&:Ja>CS}=6y~;C,#q-R6~Ѳ.kOw9ӟd^<%j>yV_f)O{~ZNVVu\:#{t#Z޻*8*ygO`!>A 2Ia ;-S˖)}a< ]xp@CL-; m湪_+)2A'iA/8& r07HݢKO-%%}& f&L tދ@}o1p 2< NtgۿE]4 ̧QU Ux2.g!>ADd`<(ծA D5&blWo˳`d/ *ءM[9_c`O?xVwxp/g!vXņ2 s1WEpTa>?iҤ8$u?/&\O8Hd[EFBɈa /HQ9rm –lF_袙CYnZDV V56~24`,Q j khJäyo3E#@zP6 @ ~^5\#} @|1o3~xÀ_뻬~ܤJ&#A#3M'Uϰxbr7;g O &!']{pر2q)4<,|wтi(Gu/[{=>[vQ~> j3POm qϨ({ XP)vCfi#]v a΂3<֙VU-&n5裏.5 ;IC =444 `q&X +S JUaܸqjRƸI6"D|GPИa䠌@#'c 6HAc_y2۶j8"?/Gp @sQe%i(HEa̘1?lȁ+4"EmoJQ?|@Ae<3X`Ђ=f~]v5 `wu ]p3<f2` (enjH"vp>ʗk}0ê45|gٳrQV_HRw)!̥{,8'OMeiowbA; hox'k:UOiZ; sM/B>#uKC aMd>cK7 > ^ةaƌ}W R k{ %K{:mt&`zq̋ TYPm@܉$ sϵ`]HPk"ЉJ . gmuIE<QAkދƏo !<C;O5Y=zNj5 /+RV~ _ёܓ0LgyD5.?m=o߉YTHvv2ik;=y߈4BD#\ cfv@'xMD[0 :Q78oJ Ə3Svy,3ޫqmfk:s>vygFȻ{*S|8xYx4Z1EN]eev}ݶ5(l)RPg6e_jHk#WR44P  )Aji@&DFR ˜PCw9ԀDY>kSD(0v$\w *5~F1N_.bؑ(:#+D3`x6x12:X/#htwAzqt>'f+ucG'tg8{D~N6mpө/bp6+]CiFRTnDMg8=IVN1+tBҦ^ə3|Jnyl)hX筁 2 -2w_ϛSs*[A[lkgSN9]璀=Heo?#XsO r:>ifsyꩧR<Iꮁ|o߼;t+2_AՀLq}g{_ @H"O AAAUPԧ%K. Ɖ'Fq^bTiFM.["WW &"?/OU= ?3QZDA6s01YRKg[,h(8Ð-B!:Hwö\_&Bu̘B6ca?; c^?p4D2$.Ћ:bLy#x(D^ o7.2XSDA Y` 2KMa#s UvqDOܧm,t2'k!T̤>)ݏa'3 p);2Ms=0K,KXM'|Kꖍ>vIv&c̣6`ql5qi}^4JIM=`>HMɚ4;tޱ؁T`jmCC:@o [iiЋjrDMt#5O 票YYɡl`MX/+J/JGB߬PҦhPu1tc1H8裏 uӪ1Ԍ3;_8'gz衭KYb0r!R.}QILJPvVP8c Mjp#DI5O=V\qECh4@#T@B 쮧MnaykLI:k#uY`"3)齌"w,ThOwz`H~tEoTrSaM0A|; MIUc$CsNcY)̛g!Ď (]_W-5:5ϵKۧ[%k] ]o<-H:ꫯ< k{^5[ع= ȍ$h h$td ̀F[o5M~t"$, F*3,qD28R`A0a" | O7X!E MEr6KWւ\CFWǗrMnh[n?`cT"yY^ЋN>=/UD=0J4~8xf`+S}Z_A7p-2߇>Vs S)Tk{)rp)9&lb>쳢4zVF7_.(^zi +[3s}s`sDE +Q|'|x:V #i]rcita!, LMZ*_#:8O܃ +nao.@C;(@C  RKmZؤG58Y[t30/5"̯UW_}5!a¢Я ̱k+#k9(7|ӂ0.OBgY8'᪫2Bc\ש\~fl>FN+ 4-G8qL=#^plM}/*qcgV lC=?omZW6=5 [cVhg=\ƱN-i!G^?:MH>[^[Q^zi믷WPΏFR{vWy{Sn)c?H6gmcn^a~*HJ2ϸAB/E-;6Hԫ/{G:+l`gT/KWn֐01u;3^`.v!T]%=k?c=b?Sгa @C.%T*h3 @Cgz+Y%]$>Ɖ?4saXb ÂE+7.pq'0ϴRi}PP-KK.\vez[`) jL;IB4)zl)0rdEIpO@Hb Hѻ΁~''&kt"3<6"Dvlyccw"J$*DuLNrZfrfu`k`w҃*No>`)qj=X4{Z\?M)S_w8_@W}|PI_8>\A^xaYհ{Ow^?:c5 0AVƖu`M%;ϬuZ'4kB06ҩLq1ir6[ezFݩu $HH9Q1 U@4;n]w[0鄧G!C'l|IMx ڨF_:qXz41vѩ:$@F=/5#8`CYcѯk9ӟF1?l]:whs(3f0DՋXTͮzV"9#ty~L3e͍BE|曻>x pJ0R43AjyiRyNGydIN;@-]Uq{98 iuq]ۈz0ZP)0Vume=E{u)!k_)%}^A"U9v*6j9lqI_bbU]` 9DXT i5glb?H4 ڒk J GO11J7 _} P` Gm8%WkZEcƌ1&L0ovV{mxmbu`Zw>lk'(Ό+8kE6 n8~4u=IN+2ǵ//BBD?E oS~ʣg{,G \n\%O'{e)3j =R9{LQtω E5Fn sLQO?:ڜ矷^:O $= ?wz6W'XtU]vŵPh?! 7D?XՇ16j(CZ Y )1br- ,kl?\ l~~,?xj\~R9~J{-ό޷+@C:+P mzCɌ$#ޤI(c=O;HlC bر-=s)}(1>5bq`Czט{Ͱal-ܒ(o"!U7Իh7~󝜴4w 0&u%1yNXpm: =}뵺K䓞6ms_X[#l (olrU4iŻLN bp: ycoo;P0;h,=(,ԩѱ5|v7]'17dn㚈 5p;]*ZC-Ak~Iሦuش! <5zW? as`v{-袮NZ70&Md3Ϙ:hB;hC;(Ǐ,vݤc"Z})g?&ʴ=O ycZ-L @CGB4TCKh(Y6i  /l9M [N`0F4K]p8ŝl[o 3ԩSSoc.#GLȾ}G"<6S`[gubmckfpvRROu]i{vޥ^t#G*] 4S?Ѷrd h>E,?Ӂ?,KoD˻>uf*CgDĤb 9V[͵~9i~QA׀D#o/[gwr-a}Op~F}6%8Yg1ɚVy v=3L։trD?-1wE>*YnWNG в 5HwXig sAfj =Z_2 @C:AsXԻLn. O׺(ieKT: t;'?(W_}u^/O\=lD}]XȁoN wD_;pB @LC_XI_[l1"a)qs+?aɫ> Zl41TG\-v=k-J-(VN."1;rDkq"J{9?ؕjTհ:'Ov8%Hw`=e?潲N^<يx)L/O;}FX 0ȲY^vef6G{H*H[06kLN;5 : $ XmQ5o 97_I iTݴ]9Ϭ 35  P/dZ9ЫaY=YwqM @ 1eCn!C8 y &8N2u?󩧩S>|g`XՀ@:2 MLQHd@A'ywؿ?at9c"E9cq'-^|Eeƌq/ Cs^ -wuKdV +ֵHV$?YfýYva}BXk]r4~}ݷ~#/ 53 <7^z'WpF3U-,}RP(c ʎ3 x嗣^zqPFORbԏ$(Cbp|WXK<j[(LlQv}4 b b䌘;7|YyƲFh+$-+]$j4l07`o$EjBue/@H\Z6d˩ jH~{%]$QYg5\O}'RGD]tv%O*[o=mhw3H״QD!H#JHzќs+f[DcG^$#i|/Mf ؑH1Q~}]~VX80Ev[$+{'ǖT}/L$"IIݒ뮻[[SnF[Ė:ZiУG*Q[9'\p׺k4Aߒo5h[[ AofiL"}AsR$_nҙ1wq9ي L <1]E"^ri(-9#e( *\o_׾VW}<0""I4tcA T7dkE(g?Y4qD[UnE{~k m\n4IC0a_6w&8.z&džDFyqhs$au#=q5˸ ЎY̼M^ :^GX?__ )E=NfEa*) \DzjߏU5 ;vl$,X )@.8 ZZmL   2G1bDHXm)|@RcbD1VM| I0B?}mA?]u@CuG@4TCKh(azMZa"v -dm+l%j^MyWψ.!6vQ^{m/[8Oq\֡,[ Dqӏaۢ` Z|{p O/aiWa]:H g}f}Z%}qcH0BQl<7 PQVڊK;@lj]myOT&zP Nџi .[_i1^--`|G,- $̈́@{`u*,Ɋy/>HR4fkwVFhHjh}4 %Ds^yHoY,HRR=&GSN(9ջ.3 #UrXbYD`-ᩧjǢH㧟~j K 3w,N;4W$N[nwau $IB 5࿇H(FqBʳbn=(@_gmZ&{2`?*CШh)7>/np(YfUVe][w_,IدDr1'*XePܒU6*eZi4#=f/P B,0YT1Һ/<#x!Pu5{*T&(:Hۓ-qHw9/U}=6|svBEx\ۦr_rAXꫯhEp3J:)FfmVN1`o| ZYh&8щ'(|ԴPgvctd ޣқ~ pK*_-.ziT A~5{oC@AJAy?O`p},i[$/1\zʼnU"?,ҽ@K3>S7\rt|kQA/Q 5O;V{f5 _ր D8{ :8,PaÆI5;찃>#FF WFCލᗩT]ӀPb;݉}_a?#Hm 6 #F@iFIK/dbh$'K1]X@nfkI.n|T,Zϵg|߻B*)j:E#ːӣzx9358zZ*]LRfIt\]ґTI]U@Nw7tSWedvJ\W]Fv#)\+HҶ.lNvkO?{q8\; -aL^}UntBF͊C$ǮyY#kjmy HP_ ۮmQHJцS}2$e@].fe&'RZ-j࣏>_o/~ #TKD ($٠B^ w\ ըsMoK,a0&E?a*pӅPMu3%`Bڰ8$۝!kmxNqN8; F8t8WHX#FhDfka{gU$ FwQ$z۵wD3}P(cYD9a.sudyT3v}I)ѕc`0(c{ҦvQ".{rM."QF"ܵK58Tdyt\%(7`b}O ZZMڍ3O"檫2*`$-i}SpKz oF N=`mZnZj)oE@ iA#m8%%i{3Oұ._Kg=zFHDFFaVZpc?{|*6@,]k  ߅M _Ćh 53#塇231eh tmdcm \xᅱ "7Hc w!l !#:d628.~\wuAߌp?Xcfs/s^@N%nj~f.Wt:m* ;.b[atpj }Pz. 9Pn5 j0tAnbJ+}!8>? 䵌t,N&JtF8ێw,m<ꨣҼOi cI;AoN Պ An4%AZ@;xIn^3cƌ (}#s9'ëh~U&wz舔ko˜^#e (nVsz=z~K>?_}HjI>UX]UI׶`g S6VN3kқ$Z[!E[ۦϯoF+7a\z5h;cy3 Afj/R[+kQ[l .,u8nx&S۵^8QOËQ2z^*r)JAj :\1#:dĢ!xV~!B3tePXj[tPBx~^v",k{Yy*ZzS{0`1!L.,@IDAT"Aj# ~EޡVW4|'P^޿CYiq1FWA_џQz\ҙHzMzd̘1Z\su)5cJVzL;#}zY0& ֖9Ϧh{ \se׈eKPznA9N#0)bH5cбH9R:]Nnꖵk Kن$R- @C;rw0Tz:̴$?7S4[H2 &#!گ1NN?.tF+ >e;bĈ:jw?pl zQ`r}DO?ݬڕ;'ZÝ-ϝ^i@9QIotz%b1/|2.Rqǻ t e믿޵GdmYǃcjYy4LP]~\^[^έIC V(,A-SG;/amW_}uvYо_N-$ƍ7 s" Xʋ/e`S!C-W\1=# +J`3WXaҮH1q7ڔm?0rHʢ_vrYg t(|3=)Xl0_vi!U٧}[$nSM{hof -k H#E2,,~~k+@F% ],E~G0?h~QYݜ}OHѭ`!I-Q?_|EE|tG]W_]B蕱Æ scSOFJ${st^{-ƒ3ƛ{(IJ<iu#f= ?=mՋLe[6@WqοO| +s9gu O@#reNmF $=$Nl&͸"q`yW^h>yop[[xChf C‡~v՘/.a^вX RZR?7uogydpF/gBJ{ &%㭼fL O>Ėz,[>PVFvcW ~$H\G>@Cz<)XDff+M4z$+_Wiߗ2&M{Kq$$<"{%D <{LZo 7aۧLZE*E>߸w|ɾU"P FGu3eʔ,.20,10qS!} c+Qtg'QrKq]ȟ(_ҵk_Z&y!%?G2ythwNʴ}$N`L[]`"1添>e܆YLt v=3 AE>oPa4,sdmdzK8x#nq_}}4{ڿy`Hmuy9S,gM73L'o{]p4p饗ƞ/ @4 I *vhk5 b?O.ĬiE OWYe 8~x.TMz꩘YK2yV30*B^W_}5K\ ?JKD(g {7̺RY~N})v^r%?+OEGrIBTH4W`76vtcH2j(fUt"}Z}p*1GZ[ř4e>#5:}激 +wO?̚ƞ*AflR0Oa&h0XH012v׿^HVcXm3,mXA*c={ ƅ .,yAE#@7?ӝ{1qڴiMK$Uއl^z˺qN%\yenu2.Yƻpz:*X|'k{f545 6y+:[D[gubcj噚utS>ERNS;CK&Nc`> Ft5{ &X Ӏsp:7Pm-|] M.pU5:7t>[r^|}y뭷,Q.'[킄`8qJm* +UH=ʧβѝ~o} n&׷1JD?/>UԩVS(y>Zz_|EH U?oO06GD2yeorO1?SxO fg6lSz#ϪE{P]H {ث?}РAlj㔔)Uں]i{ ~>CMm<,)cKJ8I^xE]Rѹe3MdM\-wt+d|XXfick-֬0]&MlJ:RFY0WBo/ /aRa3V`kup&iP¸mlMض}!l1Aj J@Cz;u1 "wU$+ڮ:`+IJvMu]fO*!t#mt%%I}]w챱3,l喱19yʓ 7lg?DaUI0h\[ϗ|駗N8{Bî"Wu@G'|+d0rWQp蘅S4O 8 ?͝ߟcƌ)DCx5hXS'hW/+ɜWu63O0wnHWf0w>3'YWx@*`Bc( v0݆o m}ChzʷP|݌A]'i]KPi8|rUm ,CNgYDvS!C8=T%vN&JǨcwaxNz03b5'ދ/hoX1 i>`Jܢ {Fl#"l(juToʴD ԯ\N?N_~k㷿ms+r:Wvc*3^8܃dލDAߊ:}r{Ѧ}6 ;c$?m64e .D/G)qPvs+eVC|+ڛy `#X>38k'|2a-6)S >, )8cB82iܿ@C7bu;&4U}뭷\YKp L^k ďW h_VCˮ#1n5t:Q0Fw[/Jc #"lں{V_D4fhuE2(SNEa+ST@?zir MvکS<6kgޝX%}g;kU]wN<1Uzo7`}CEu-𫯾ڛt@DNTWꭷjQuѸqO zdK.˴iӜN[7tSUq-2N:Xy}Ap8⋝Lx2'ۄ رc Lg0E I1~Wy%RUg uYp@} @C}~\4<&]aI[X 0u7:(KÎE ,0PTkF Map }HcH 4Ǩ3o Zqݽ@ۊK;>׀vu]gCMa.iRal6oUw:>U>ʻ&G幺ߏU]:,7f TuuD/BNﰂ5%ߓJ55JxV!2c?o,Uj͝ku{ᄆvJϝTW^y~bOr}O?0I<5/)\%_Ek6hHl83ѣ[ݽ k*a WiC+ x P4 ]/W'3]fvZʰx&9cӅVyС6{y}]O",bV_}u `!_Ҽ i7}@y8 U@d~9;&T e1p@ě91?poq!,0sLn0 Gc";$8y,1_bi4fjtFzv=V6}>կ~Cɩj7ߌ9X;L<5H=;,~D`(kR>/6Eb׮R>`gjmoA5)i!C I7 A@ovg19[G>CipL@n4ߖ裏6 u}/䒹o+ y)zp5/6CC,Z@f}o~Nrtbö카|:_3a$-#H8-U'.8`gG}_}ՙ곘Q6y" z\v;i;SIi񼄆ʲn9}5XþfhǏwmml^ZǕ+ҵE"w^z*B0m;LYAfj`-pz,Dte328c{fsPj03k{7ZΘ1cbދb̻."C:D@K6tϖ:&y/ҋ[c=ft܍1i bYlS]/fԩ9ug"umny䑮 ?~wVpV@4믛? >%`z@4M~ *rhce4;n-K2 ѽLǍg@'L㳉>\q]{Ͷ8*^k @8I=hyq, {8=?qjBvw}2ĉ]Cs[4 ߳ELщy'Sm@{(v۹#O8}c63<|gU + n{؏x? <@K:X\ASK5xOPznVdΪj?Zޯ|hVOo8֦?9r-Ysl|_-)|E]w*:V׽|tس|]ff"/9dѮ}o;O~zY); |M7sc~{ߋ?O8*Pܤy6An AA%Ѐ8ӧۖ!%hUo g?Y$FH"#1Db1 R3b?)b,ߢw}7H0anE i,H :B j)Dsʲ#ƭHlsĀ =AHh_ &XU][$R׿u$@ &β8 F$W#aUq~F|p$/Snhsqw H iyjOt=DbЍzw4iҤHr_Hh5tMHswifF:Hϳ~8s=^9jc2O*fw"v#Lc Q ˓?VY/iqSt.Jo=3D !T(ߚ[anپsx޶%maۻq!h`F]s<Gs][M码i 04tYDoQð!SOuwl(sɃӱ9r@w]H =[eq"_䠗`ٚr{h腢'67Nq[6z/*q՝*yUUqg~r 4@e>RMn鬃JG;)IZkkfݵ_[ڎ$kzb'tRJ_~듏?IP/2N?D.mZ$ZM[P6)҈w$i~󟛳:2]@5+γ܏xSfo|a[:i HU~ߚ^zɦa}Oȟ}Xy&!*?/O> "q$yǚOj@oO7E)ҕ`eH?Ah9RZM#Acb4}33R_nKH9Q^m&_ $dX'- $ į?ܐ'q{4z $/f>TtzD̨!SOuwַęAH;i&r/b#fEqm|1L-ĘXl%yE9@*EMנϣ|'frFZ 50c 3j(+ށe}@Y5a&1V{ ZƁ]5`Yxchʤ,w<`(;g L~=:[X i۸ Ѝ7ciDjai^VZJ,i_?I1d9d@*=n,T 76uz ,]jX5aNjp\@K*~Йޗ-$R^s5?lx{1԰) )*E 4P $?ޝ %:PA뫔OفRJBAUF8Bw2 O?`t!D^ Y#ʙK9(Z<_>aǧ('CXh%I@ }tYħq%TXU^F†p W4vhwIt(ʡ0TkYIǼ(L u \t9wy'"eɵTʏ.ܧeAasF3޾I4s%;mZ)uXn$-fR1cQgt֎V&s(XR C$2H9#lmUHCBQ>jk+m~衇r]mYK$FaOI6{-24 LΠ/MriCj6Fo1ݠZ\xᅙDi6g$hm(ÉD])N24+6$ +806ПZ<eCDs䠃>m'A5m:Lt 8СCm<%x`ճ:yFFa^r 740h̆^JRLjJAZ׀ςL 3[aӫ5# W{4;/{G`g zd #}/̄ ̙gi$2JR0v#w~D F~ lJ-v(H0&;k]35HCCcאr=W) @Cݍ1NJyB$nZ8q@ nՠ`PHʺnTVI0-yG1 I4I*K..XXP/!ϓŕn}cg:q*5MzWҝabѬL6BYNJ *'rj{%!-Uʖ{ު \_/`@Y꧚»!Hc @;R-Nt'Afj]w9'`n ZI=2k0Uap:%liUWIӧOѣG~sm {{uV*41}<VAÚ/O 88f?cm3[})e7) )B>{:nviO,жbwὊ]GmJQ8 xڠwM?$Hqt9ش4MV,um' ;`-iHi[Xp 6D揙Fݤz׭9Y>ghӱuүMF99ϚFBjRxZs׮`i ** j Z38c d_ ,xɛ\jk,!Tj6*B21c7abH':XhbQ:-S.nG0m|vu!,EHkc4 2P8UGlA!4^}C7hq~E삑I#usyj]rˮ纱mTG=0REv(i64;}6Jqы*NŘ9GILLs/( 2T_VQDVR$ <(|XV'zQ1`94@;tIki.dӾ`Y6a~ OX1hРCW=P7nLMA;zOYs=Ho4@Y- 0*;NAAav  V<%ANYWڌV Ҳ*Áu4 [ vf!#C-nqGiY{n)PqwC;m$_%wFPm@XHM#-D0QӉ{Ec| eL`h!}Z 5@2n,,N8Gec99<˸q\]NԩS^\pA]'!D{᧮W&|n R[{~ sNdذaxnt y=)ad:*YW{p7z{?+-̿I'dn;2 1vE-_*i:@"Rʅ-Rƽ\6gy\Hhj-_|lm{^yL̀0k-))FU@^z { A$(~ceʺa sSXޭLZglӦMmC[M]i$`7MAU@:a|@Csh(sUNPqc' /ygT_zvF ~}JD+h^UT,]՛UF C }?E;9M6SOnYWyeҤID+%O}GTVLBԱ>wWk,.Zi "+ wmv"Q|dH\^u馛1Y UFqXk1Oz[F(cu>um3aׯ,E>dLcUgedCc eĉy=1=E d{l@7"I^ O7 |6Mn.XCn4/ʪX]wXJ!}')qQVJ|{6L)Ak G@C{7r]O0@{-4µ:Q8r!"6[`p`Ѧm@He U=ꨣ,5$v>VSD;%7, U?h<+g}!74XeυJxFWXt?"]7Gs￿7.U%%7 ,{gςf7M 2PbT׀ z§T5Oyq0bޅCdoYhbۇ *+lᆮ8H[j+ZhIDMXz*@PGC=ymA-/of]h 5TlyѪ7>+_V}x+^hP >m͎-~ ?.عh\8ǘ'yk_w}-{z[춤dرv7}t}-HkT֣=UI @CnA &{[b~KNRP`ѣq~i.J~K!@1 Һp<%rVXaZ;h >fѲC(׍jrok?3>͢R94Onr10$uaB%v gh@*qg:1 G#~x v|oc±@&^]RK-eYHgꪨ,i2sSN9Ů`Gi5Y-pe֥t"3f0@m[[q^ֺ%Eo?k "18܇ dkg~ wڇHT- hXNYcbc~6x`5 RhOSX7x|Fyc\2D~isW: :Wc|ʡҜ[d*D: qBTrI跑R>BVp<'D. +Q)+mJ-gYerDZg:}Wmry@bh%!;C'K 'tsq-D:>l+'3.Itfɪ~<'Q%ײPT8Or }Zˉɠ簓o'x_feH;Xˉ4hPc J >xґ7DɠAq'D/')ATNR9u$ߺZv#*Wgs=D?ku۞C$IA' ~*DŞvzK|PGQџ6VXa;jܔ)S wB D?wYnϳ׏(V䮩BcBmZKGdz۰t{% Rܰa|xתC9 yDa`0*E/o im^RQUbB}VZ)LBC%6!CH/Rt:k QA'|;uM@Lh9&2nmu8$I.ǎ&,ŦS&x0q|N?wC q&M d[D8[/BU r g:VJM%/l'yK?.<^lp0O+q:٩n&5\3Weulgf! `N"~ 8!x{@"M Y#{`$pz`,Jԙ(P:Qua%&rSo!BXPCm\n_[nMF@ C 'Xʉ'R"_ҡfB<2 󉥫 rtS(ri}"5HN7ҲH /q$21k1DsjkT %bGG E:ܑ$:\0`@N;%p ѣ|5\s";Ƣ> BӂrO!_3`'C^]'N<5m/4  up\ˀmA*ZA5r8g$4!}QO/hxN8Z'Lv^V"T\-brxG+VN4!U&r&FT<,BTKnDѺ]C$)[RH_\8{ 8{lB GpXT QUM' \B~HFI<}nB>;}SR%?jLs,=6`h6riMM - Rf]`3?|)~N9؂JH֥3w{wVZZ)I@j>!6nJ|o!>{w-cT n?4Lnf3C%Bn"ە&VXorUb;4 a*3[Q//."_j>6|pwwx4EHH"#{wqyPrDcDNq_ $F}42jDwb GdlȔ#⋇M\?5m?ǿ/J+/:Huɟ8T狗/#:BR )jDcȣq$o;Dʣc=`*B K"k1p@kDAo ?dDAb BB G/y1`mRy3H EBvBgxAƻR|M y|B'%,Rzr<)J#. D 2 B<[}ɢ`<:F_RH@b@J"5*iwNiA˶5R D`tf`sͪ#hoF5k,|I> pIDf2$mlk}pvi8iqTz#:JI TIdNrF8p'H/Ԗׁ H2s)| sSSBEySghVKwdrӘ;x+)E8g'R+)+Fnv<""|Yi_ɀQǏE,tsj !sB@7|l\p]-=`ذaީCR ]f8_#G'\9YlO#4q9:pQ՜(H++ܓ4T!.(ACcAQr:xp*`FjyVzA넾fl<QS$@Ń:]pTcDJ!C71Iw_~ٽ[)^Qx/ffM6q<jGDyĆ Ym1(lf$A7rƷ1dMx1_{<rHA5A};br a b1tO~|_ 8ո*HiyRSo%i-zUАC@Fhzx>Z3k? >0p$ *RD!6c@BJYrO%: 3rp~K<~8dd}U5}~ƶHU3⡘Gvo#4t`>!`ʇz*[^a L 55o/G NGG,,y?f13g.0yT˱!A"s}JQ~Crm[;P ";(YBlöIWWnHxJwHMq3H.Lb=NXf#ӌH`Ԉ 馛 @n"JD^pFe޿} =\4 X❅Dn#W  e ;mg_HDʿ(}b~ZciUd(ߤ^Gqrq??Jg:uOIO1R;{/o4dxCrs8MOeZ cƌ $ 7RFBHټH}m!.-b / loE(m7!O aq]@lI)p%GC>%Vx<hs"e * ϙuFh\Fh(-72 OZqT= `EAG}#HA#̚@|( 9ITic"yqVkX6xcOb@%f@' 6%BO7S=;(N>Yz|t>jn!#C\f!o3 Ҫƀu38 R#^q 4뒁F9x_Ө;+:}ZG.DxzbP&7nlgyfHe//7 ۶%00EyPrDjѻm' qO܁c3 pqoSGѨ~w%U "Do6d{neVp/bX׮yB4;L8F#S13aqo޽{ޝ'x}7{NeEY2W]uWCJ*}-S .Fby "]b-V o7 u+5RE˝s5T*% r(m%3eu][#44Q+h#?yI6V准L]͚ ͡w|^ژm<GvW"q~ă H}U %gw3x'pSA;w63 "\ Hx^iءVtHԢw$z] ŋ{u]֬)kA8)bglq] {a:6+8(=۷:ޯx~Сk#}] NvJ8UVYœP7PkTu-YS)-+=h \juŁC AĬvQ1|4i|kc`j/{BHBAb@u:RtgD 08I͔'ĉiTa#k NpUCqPGov6 k|&l8Yp8@b@MCJaBTQ\S{U*-3 l"s"V(EzeSWdUU= 4~v'm+ )f6sDHǀ#k^GQY5(A~Wu*,WpWZw n0S<F?OZeZ/^a0*j䯆t>x'|8<'MîcOvoB9+&>Y?&mrcܗ7QFy%@ k/ {H{EIघ!J~_K:ri zSըk1^F_206;!6@/6ۨ_A-nxfO>ᚤ1sd)uח2,!H!*i+H \+f:A03CTv"ND>.AM$Z"Q h%rKd0Ж)/)4onp@t8n&OK>2zݕJC"LY]Ѿ+^&u O8׆8^vT@,J(š\f{"}0r  ,&BrH$eW"28s5Aqx..Ew$Ėe]a% Duw燺++-D"O>zigQrHD9J$W9dʔ)>%B0bI뚅I쀧BS_C=4`M.($BXKDq&)" Lx0 \vv۞=8dȐ14F&B,#圉:P"ijCXx|ND*0 mdtIXH: Ͳ($) *喤SrUO!f03zDf b`v'36fՆ+]a #Wɹ 0i*S Ht qȜ> j /BIQ e*:d0:Ր'K.l*|n6@_`qi4ι]_WAM*xnrDٚ"@3DA ":i1>U[>iEUBC@=G y(dy YY}ս*3Ʌ,UV> ʠ$WmHCpU nƦPH w]~xl|%1=[j"BEԪk:nV={GGkD/{fB=@k^OUN$=P"o`M"%Sh`1((༜ok!Xg.N8!+UoI=_)cDor:(`F3 _B6 HƎ\z>:+HL|Wi6r@{@]SSߧzHBSS*7>WP4X+tFi_9*A jEz3SBrqx/}L_NPYםy%MOnT `A;t %`3,{'B 5KKIm-EZ wFd!#$7VɗYbi~-Hy3|'8qΛU;3;˽ˇ0=HضU*?z[?74KX;FeG@ƓH\Ӗk6~Kͯk!`Uk$NoQ؀vq)?@ڪ>'Q ('%gܧ/[d`?ɜsnh{*|i-iW_ x01<sV `/-)4G}?\sTNTy Q=>|OןN_yHEʭZ++NkUV[uX;N 5 R ,ӧIs̑ >!R:Di!bf4 /ˇuwGnn'^f[H ng+ E:y!]eV~dM u2x\uDqK"ʺvDzDEZUW]Վd2($#_ۂ!iH .z"u̲<)dЫǦE_dAQz60 Cq45D6ܬ4ǤÐ,;7sgi5כ`jL9e;GR(BaR,!}L_|ѿI ;!O PKۇa&NЎ_|O!3sҎrqZ}t| ٥!iHr!@)4]j{W}5%DBؔ)S\#;5=F{0.&D7rHbO?ݧuYrNn6"^ (Ud)5חTwjYf 0T_ NTr%lWnZO7!` `OjвmB3;IԼ#TMԊ\'xbbmD^ (RwXh3% H\i'$:*q})hmZ85_!,0a_ H5Il|AI&&bm I; yaE"KKt4hPi[kO>۲[ozZys=."֥S(Dh 7DjpJT;#7jF}+?MqBER; $5۷S2 uT2ꗅO[KϬvz衰q 6SKcJ+PA]qvrJ9jV >$Չ_ctYf ЊR&h % 22C`$6`ô&/F$4CHo&HYbL$*&)3R!` 2 (@>Of%5oͳ@Nu]óg 428 Mxd)~ !yPQH)ׅЛvZ WTL0!A濔/y{eе\.c&K!`>K)ȿs9&M_&6[D&wqGӏ۬T֬\ > Ɯrq{ lEm1簔/8`;T6QIkP2y7% zK@DrGf:%&)ߴ=Bl[jV].­:ro8[/!$nEf8Ee}HpjŃ2CtP/RvmWpV^yԟ'H? PI?p /;{GJK8cf! yq>kVjaI7J;v*"Cْ x4b;IȢ{,kԩ(q,&2Sɛ洼k[n!s^RP浩 iV[m:SRf%}w'x]3 mY e36BZXzK*B(;o[!Ub(|hB]2 'V"Ͳdbʮ3%M8[I HB!hhDUuVj Y~{qMelw6B9~Κ#ڢO>,;\fPP?Plvg] ߷Dd?ߠ9$GHْ6 jWr&30Cu_c#gi! - ~hqd>IaU=_b[J:(iJ_nr%w}7A6T\`ꪫI-mCL?w"]{ޕP_mg]~a܃^#d(9Q7oFKkN!Z>xB1Z>ijũ~65w\A`iF`j!,q: acf4*(S$ ~Wc6s7tS7 6c# NS'aü|(;w;k32 Ĺ +W_tD8qgIm +.'N"3k-=38QqmkaڴiNDg:b\"~'RNҠRc@SNBNy| -TǴ'{' ?\ (;s%($I߆r{>!wQG9q^K.q:kXf3I K,>C_w/;{~iǵ* k;qW$\7~H5WoV7_&N4m;tt#рe㶪pI%)4rLytQ68_X`3 A\/r'W'<_DV!eIV!xnNRKa ;ew휐f}nz{/WmENqv1 Q 믿e?0͟N5 c,ID{طUp0ޢ&$袋OQYnWPK?v 4 M CZ GOlt1?c(Bj|HDKyA"&85Ǝ~gu@焩qR+0ڴ՛c5u?J.I'yc r?I۰3;4 yDu[t{vJdz~13 C `',qyBN_w  U"=QH=\'*:ȦY%4DM :H;IK G E=b2mLIXn9QjJ;$ŗŵ@,0'݁h۔P|@d`9u*; "$eߘqmlG۪*C9BDw}tQG4 ʆCujkN;4ّSm%Ճ'~׎>:QDp11 $ZYW<"C%A _lX+\lc;x㍳ܔ;޵J6oNTb*"Ɨ-V!P"TP1$GċV] e953!ꫯUbXѡ3 Z2>ǩAF"D&-n]vK"<*kG(暀 @w>sL3Gሞhƀ1*J` Al9|hJ+!P+ Ph~e{#f`#nHDc "a>PCfg@L3X+gu;C"*pZUԱBhh$It_$} 0D㤥,j|gqs@jASb"+q` <NOp c8 P R:{+I睭yx;ά_,B8Ygկw^z)cǐ a 0R B aOwx??5}jiSc)&8.vBm)E@{"}C3C0ZZr^ʩV2c۾C Vh0BwټQFy;"cq6-*>Vil?34tYv6xȇRCMI3 29Row\<@A*RfP)i%0 !mH)l!`d@M+VnrDFizb2P@9 4BrFb#x?A6ج4;pҗaa{A{G}44id*5/n !|`p + Jqⱦ'x!hYQ߆>JL]N=Ԃ1 /v)"Pe  vi.VJ!A7]W@%80IRu#݆zaǽ$ȑ8]OʑZ,MS2oj: `'e|!`B BÎll|of +~t$q޺buNF:r#P..γӖ}>Tq\(9VX @"p '| b[Q׿5C3 H+iܗ228J`ZsWڼ!`yAjytInM6.GQkHFzSN9^%muK;0!C &T\U͔7QR#c=Tw2SRFiQlq_O6Om' wu~?};TTAҶ=_F'Dl< v}Ёh4uT׫Wx\3As&4fvs|Sl7"?o /u̦ ھQ)9\Z]9GqWPyWOz8p2&:(4@y)>7=^S21 7?T t@Pϴ On'>}% 23 CՈw03#Z ymc#!PShp^rnʔ)]`c)VEFvD,RԀGc%TCG]S ǒMNDy`1pbnh<缮W0?8`gy2g r}H:v dq~*Co 0T'j)H@ ڧQnl {"l Prĉ}?TސkZ޽<#:?:@:ed%8Pu[E]SV.T dE:*ǔ<)[ˇԇ ڌ[l|50ҽys 7BC`&oD cb/仕wRP1+}VRj>E*.gCAK#F] Px7FK8kJeuʸJ/1I@GVۚz{'DA$BPeu^<\r뭷&Bж!`C/x 2D \N(`Ս897DCpm%Xq&uW^7[v*!TGDҺs(rɻHp-?ps)O"Z!@))_ހB -55ovn1kQsR!$d2%%p"d Y N( a{ u1_~ُK\r%OQI_Dȟ?ZN~{-T~ucg= mY CW : Sh7Y?IJO&|k28&90t;%B9JdpciA1"6#oa2,;i&5XHn6Mda xĈ>?/%6rf!@wժvk\@$_툌@Js@"rrF"ٝ-^}dv3 NFଳg!eP\Ei#9)>C 0K@LU-vGBm%YFpoZ/#D#`DB3Ruހ,Om}>!?\O48On\}ơ Q̟`'¼7-#Y2N)ϳ8|- <Qd<6Q 8ݒK. @Qt|{~(uwφnJ0_ P .0tUt`,+eJ)݊*5mY6!|x_|14g : l0 CoXkkEXA.n>f*8A1).DBYiL!I`5҉=E´a(fDAuB?.}@yʼn9СCC9Z z E Ϳ#l%u4c"=eٌ!`@,&Le{.-+rg}vU% %}-.q0WUm=D&*gy+l1cP"0+qJޫ|HZ;p ^qkGq䰕kvBoށ:dI3p}`wD1>ՌW _%<쳅_-A7 NRT^X[ő.9ONyH;"'p;.iBCer-a.w۸o.*[qr ɸc`#=5nCڴv}]wUQ^TsYB$@Jw}}UJ"cezP|DܼKuUg=MTA2׎y=K@an{ˤ V?C0 М 4ܪXkFʟA8'ݪEN#JRl!DDivP!l^mٳK/w8Irz2?^0.u7u.D#/"7iҤ7tS/QL 8>`̪"!`@hlFjͶۿbe_}Cg~?%9_UF, S E(l7\بg>c#}§ 6l[p6ģp:a{N8ᄲcaB(umbawGsk^oAZksfinp`]jܥ^ZbUWuwywǬ}pDŽRi'< h HY1%N9D$X4MLC 1+"T~)S|WmwXi3!`@F:u7Xc  ):W8uQNdOӲ)g #2լ(x6m:G2a5ͺ"`_8%ldȐ!NRe>> h}ya$= ?9 hx$;cGV_ 0 ve!`u @w:zWZ;,eT^Ԑ(_q@P꫽[oՉ exoi96O>$DرJɒc0N68PQ@A}ۯ_?!=37O>s.?WOu)DQF2cDx 5C,bNr %J(.x2.*~킪5-S  Q nסaf!1d@h "s=8|^8 !!!m9NZ"PDH6E1`|'9~GA@>j!D0v4VR)$sNBmvcrH'"E0ZB~d&Tr'mQ":a{Ŷx{畨f$J$i!` &&+3Dw}$ {CT%:5|o!")?8NrHkۼ!䢂^?L_w;jD2H}$X"AIrBu{8Ɩ묻Fˉâf؉m3Ͼö~mr'B\*؆Й;6jf҃V[mΙlXńeK0G"BB&M<ȒRމ{l"D}f\ix)ZR%.RoYZ!D$x!̛uD-C@"6L&Ho2N`+$J-<;%{.3Jő,%B b;jW,B I4TIwn)9#élàHnD" b C0:JD:c !QD{ ^}uׅs!M8B}D+HT|"ZZ)B"{=LDKY;a!&fꫯ.￟&7m1 # Qls5mO*'yoO$e"a>!y7|ӞQ+B`6 K.hJ7kCR$by^;: XOk$U["pSQJ$5'3=lӷ7GC6vCh<Fhh?k(DSqRBq^xWrM?UUq!QFV%ǧ*9)tl'Nݾ^'Dwi9 Vpx!8Q.6C0RR}zrYEvN3QM^xᅝ5<˚4|||0Oi5~=M٧Kԡs^B>Ps+t%jI,Th!fȹ+RnJȍʬugvXdoYfwp-Zk$޾;ɰ6mߌGq!`@:qJgV@[C馛:l[=mH  s{w?ʬe?݇G- |ŃݵB@r"I}" ZU;!`@[X>DȔjh[Fo8n8m궭j}[q8 Zt)mS p_~y8[( j単p'{}OgHeo_7JکkZ*Z>*7-uC=P~TG+ &Y mc8g}:uCŐ$mvVIO0AJ".[+^xnF#)oE-55|p#< BH('e^yO*8qb" Uxb&Ɠgۂ!9')|bc9sε-%jK/C +yԕMʨQRW/!P |\!7[LJeدI絬xigWmc!>=CEY@}"rPI7="m]=#8dD80\hwSLq+RS}ODF[a6oǕRU2͋/{ؚj;v'3hXM-AzSCn.2eQo SNХ2_#,(Z* zR %\U>Â3Ÿ $@}g~ S 5EgRs@T6 c=g(&tF@@v0BCvU3jjfjeV^}Uo:[h|_ *F;bHhxꩧ2Ɨ_~YpuG#@J 0:IWbA˔y!`uQ^r] AO>\{g*"͙@3ЎYycb y|4_6] iԹCHODn7I'N+ :TnJd-gهYixf?uaEuHH7f#p7T2ij'(?wygAk #KUi4Y >h~uГ3\{Ydh f j,R;K}nF[j;qT `|* )0hſن4.7tSj; 2&SCԌN V=~xOC0oK7BCPNy瞒-s vn0 N'4|H!?|z\3-7g=Xە++K+Fb C0 C}oQ|5ׄJ9܆6pp\d 9- ol PD:@.{r6 qO^z-UK&ɐFb//X6]uUnwKގ+(VKxf!p}q_ܽ[a{IO48Ow8!+}׺)JtPS"QX4zAJa4R7Uou8 ;bց9r~=nS =) iO0*/ WbTv)5\MJ` #`!e'$k!-vHQ9S+e| J* 96>Mb$< P0 C0 C8Uմ)/?m`W&D8ܬgHѯ_!*TFn6[Ҥӧ{ih_`(N6-:H]O;J#pE:(R$5AޫW//rUD!Cx[O{t P}W-S_kqPS0bYy/|H=Qu7Zonf CfD#4dl_׿ԥEB>$ĉ'p;K2:bt^xnFh+o!`@@3MƠ<_cSТ8_z>}o *7n8_WEwκڎU ж&M2mlhŏUVY}UD ?|_x7#5tVcI!8Mgq r)P֓2쳏'k> Bt(&;VZƱ LSJem{&=6/ptDΪH8j&"&BߜX1z 6۴JM=ɓ [1Z8Mh GeP5Qj~lZJm}oRBGc?PJ+ [`kXN0v ,"-i / ~7x뮻viIEj< ZAt1uV(ЯABgn}cƌq#F(7|_|/#Շ+l0e& 9P&en|8#D# V*ꫯF2@ņW9˲߆!`!`!~ӟTws54VyOY/i^C:F#t dYg;ݞѣG*sOA^ 6(48EJ#PA4;4f)矇y4ތu>cTb'¨Q(8I{-kNWDZ1. U-ҫa}&NVPR*OmV߫\x4* b-T'Zve  3PA}W>$ HԐ3J/xd {0 CEZyEॗ^rG}tt.yRCxgNq#*V\qEφ/I't\rIyRStM C0G c1%A3seWOo k5{EVt7  ?`iƆ!`!`!`t^wuAnwܱhbqB"Qt g@B -m.2Q8 ȓ4۷C%#dHҰcΐvs ֫W/ T43syDc1y%\^;E,i)8[tE #Ÿq<g/yVi7GYr>㜤6l_WHk]!Ew9OSp Yl"ov+f!`G 4%2PxAD@qO/PQ@?l3a/nANwgf!`!`ΫuJԠSOzDh@\ u$b8xC~Tz uu[\g}|865rJ{LjYy衇M7Խ{|;o[wzv\#i5kEV[m51ߥBGG:<sǏAFS 1 CYRw)V@8 ?o<2A* :^j?ܧ~D]tMa3X1a":a= tryGĚo!`!GldM|Ӑ5Tc2QMn iYJOBt"G%5M c7={w5E4u[ #F:kE8vuWCMC* ;oxr:y|>N?$P :Hu6-D`n~!Y|}#ovW^y'Sr!W4h'@@SՉ (Px'?w.8/^ISC(SvzGzZ ,=Δ2EY|gA0q=\Xr@w+ܛ< RUp-3ChrHH+VL\m4!` i:)K,G2Ϟ &P>|X`!`!`@g"5k oV(U͐21쎿*( Zz.owgqvmI3<ӓ6yBfqFJt" )ѢYĥufL^QZj4\8c-Rzj qwZرcu=šq ) ^j e 7Яù~mB+WDf`)}K/],?Gwԍ6xc_Nw,x@⡇WZ(d8w˒4Lny$UW]AR =;D1cL#3dl[] C k!kgM[GoЉ G0 C0 C0:w}74 gbBCnx S =SJdЀ U:AJmO#8B2EG)P0:8ΙwGUԐv'"āIF(QK>`\f!Qń_;yuA:+P{ZveUW]kl:J2_HvPG PBn)'q݊;i֮ O*y|>̿Q!e‘G!(dw$munPD9" ,>iBHs9g))ߦO[-O*)%&*V-^}lK THusȝ}y6_5?2K(!) gLy@ }\ Hʢ_w4i_?ajΞ:u?'a!`4Shh +tSaH"bthch 7 C0 C0: ` ;Fhh BNoL/e e _6ࠏ'YSi9'&3l&啒zJ8E_yL޸q|4f!u7Mȅ:Ef=Ƅ?ϊN;{;IWP"  8,6c_|mv ZPtӎ 4BCOM08>}$@Ժ#<շ~qĉ=Y؞#?/#euY/nԨQcfP m^xa.A10 C㎎RZ\e C0 C0 C~l#4ԏyuM+C૯ ~~?, Ǐw$o%Bõ^Ub5LGI(YH'(ĎE"N;C:2j>wok2M^ ~5ҟdx""c9]ve1dPfa5|ɓ':3{)bE98{ q|]x; R)@j+BRLt阷rOTkcOb=$8qf!`ARNe meX݌v2@ ~P9 /y a!`!`G Vh#s&6W#468B=h0`@H7Aȟ{w}gS3!Gu=ztC?AjFY>} P@DN:$cS `Gu{wڱ1/yNwLbPg^ۓA\VȺ_#l]vqoWg Q֤I69A@RW!`yFrrQ@q^.qTT2C2B|FҌTf!`!`!y|Gh)4(aYUD.[Ypy[sfs-5éPu9; 3ҮЀn뭷. 33<>³s#FJqP{s+n6uP"A^ x,Jw┨m5fC_=UЀ&̐wW_p$}^᪫EuOcB3\wyy/YfGqqǹK/qn |>@ 5dŞ~i߷t[.+De-VOC0~[TbRJrqAi@_p_WUK`? C0 C0 C *8l[PqdՈviM+@w}uHcD6"ܨ( аM\SN e6̍;6NLLhx7*~Z7|K{y睺ȭ*'plAX֌g/81T7e4C^s}YuAM/PH¸^z" վK 9r.k3  nj_}\-.+^ܟsJkuY'J!wА3ܠ>m/wȣd$_c[L~/؞f!`!`!ЙĄ4IglĄK7Q^|?_s5nM7lJǎ;Z]wGܑ6Z}}xVE@@K/8 RBYtEݔ)SZ꠪,b> z$?vm%ߩza\R J!χPao(cW\qc9*aa 3PP3B"Q>p[wu k<ũM~UQ! )*?Ч!l\Ϯt0 C#4aA` 7t~^XJbanyu(B]CrYg!`!`@g t;@kww|1Ѣ .𤀬(a |1w#ufO!YIJiK;o {ilu+ '_%qJV%"6V#y']3Rn7tSۇ',@_mWYi NU#r^3$ˍ6A2FUGqF*SN9ŽkAK=FkJ눪5'zO?]m;C0 2 04!/2!0?DPPQ.0 C0 C0F:Shh9 PHnڴiaN85?/}7o6K}pLhxgC9õ:( knNv]lNv=ӟ;+t!o&{cBN_@z =#AfTHCrؖ]vYLz#H e! $ *&?#zꥳ65 C#4\/qؗH-&q3H)l03 C0 C0 FsuK}5-aÆ9"w'Li{[lPݯ4q\z饡^8z衊"a&@TQkDD)iW:VA;uyqmynn[hĸj[ƃN{S4z)꫽3;dv5\?u UboOsYgTr!$Vf0 V"`Vc~^w-o*Rw ^{m/E쥙!`!`!`Bh` ay衇zkwt΢]6Tgɓ YЀs6p5\=᰽{v-HKtBڀwyǥE"gёn\iRLM 80\7f'qL4'=2d_[/$ ߃M0A=u]PK;?|M7j(m'jy&4I>dCwj)#H^<#ەFTjYzƣ>vG4= $׸/v*/r!`Ԇj*BQh C0 C0 C(|AXe fbux G)a5HD6ej_1cƄpwqȒA WK/MډV;O=T%# 'ѣaӦM"up-QO}x^ťnW(+M2%ԍJ K\qo}٧2h C0E c+ C0 C0 C0҂@n4EϥZAc5K7Hƃ Z tj[nƏ 0.gPP2`vCE #3#c Ch Fhh V!`!ٻxo322Eɜy)CDI*Rf"SBfJdR$!C2e܁pZ:{}νF@"Y x Cã9uߙ֚_~wtFv]>S8qb4n_j4i_.ˎ}} W^G&8MSFW4t@k>|5t]]A@:Z'5e͚5|D/ZC?nH'L\|پ "\5h Tv`f40'4MK,DVӬ!<*!}10<#r )Sh*ͼ6n8{`Zl);VF 7   >8u=˖-[LrgDסcǎm24y>z{sw^s\?wIG'umMޟwޣY,<3Ӟ+W4A :L|8κ۝;,/v/gJм79/駟cgDhvsN9t̙ԇץfl:!8~]fM3 ZD':kժ%K.5.!hrʹt=,ϟdg߁ڶm+'4MT͝;7ЮG1!3f-1PP@;Lb,8ӬCZnKOBjǏ V,t?-w2{l"C"  x_@('i[@3 8OʻJ;u9!-="t2… Ƨj[pko{Wg N@S(Q;Hҹsgɛ7N'Чk׮mRk`SMF GK = * 3e^up]Qe4 >s;9q9L6G|wk!}9/,s13gK@ !l92   N5hI:s}X~n]ս{G.:&e/Ox|b5QƳ5kL)bhXlnZWȜ9lذAҧOkSRƍ!nו/h@|#@@o9    +慣pЉG;xNfC~! ,\?{i͘1Ô5k$IWy4;Eeȑ|cǎ]2zt^'vVŜ)SF֭[gRkOjƍ g4i&"-c4248YfȐh޽&'8MO:%Z(3g8Z+^zp7|#ICާMK_^f͛g6-+k;rXa% @)@@@%Mx^C x vM~ٴilݺU^j8,Yb&4wܹf@8yIOn:qF3_]<ܤ%K'~ ˗y3+ǻݱyѢEv ^h-OMy#&P㏲{nàn&@Gi'9dk&Ç@/۷Nsw)L\rx!cƌL0$LPfq iqD扲gn)޽k_ NM<^ w&A'H֣GSyer/[-Aˆ L6̠Oz hEÃg-[V.\hW*WB5POsYnhfu^ fH ]G@ jhwΊ   T@:ܽ{xNXӍ/̚5˧cF%SNԹKz)^ ,ư7ؖ-[^o!Cub}>NȻ=DȳvAE~hקGikW_}U:w,w8y3gb:qBY2lD#t򈧚-G:A 4|ce/=+:GȻ%G+h9;s>>ů `)ɓ'x>yZEO|f9~xh̘1r{OB;f׾k"E 7o]%*w^D:: Q+@@Csv@@@@lN&='|b?:wiaȑC+I<]G'52O:Gx턬 ͛7ΞSLqQ'k;ߴЉY͛K3p@kϦY4[>OO߻wO/^lNA'|ru;رcKڵ~Iz)ɒ%ͽ{ӧuʕ+WJ@@]G@ h{@@@@ 3ĝܹS{9TέKM#޴iS;5H#]tn_kر+V,3vM9ΐ! <؞Y'4 N9&4|Q=h}Ç˟?>ZHE>0z4J<[T7ߔ'N UV< ?,X`7uU/ 0+]^Z4 @]4~0@@@@g2e2 YtBAkGD[>aY^=;׭['ٲesv\E-[򓎦98m7o4_zk׮Cr(P@Fe&:^РO|/Pl@]T":tg⣥i֬dɒE{=S>{hƌMk6'J*`I&9TR? f$t@W `@@@@ h-w}:uڴi}mW^fA'_Դumzp|XfyMNc2dpo@IDAT'Zu‰6Ktr.[\ ѣo:կ__VZe3ы/4SxD QoRN-<,4hi~}Y{Y^x?ܹsǞ@+"9J\D„ v<(-Z0%=9s攌3rZɒ%M$I8X"L   D@۶me֭9sf{z`nh;xNx|fn_tL۷?X/_n_ @6m쎻v ʦO,wbŊZ{,[Ԩϑ#XCӴ˗Eko۶Mh TZ^:[pa3TZ^]tI<3|?#OC&Cy\r@:aN{UZ5i׮/-Wӧtd;{0gΜ~<͎s sy;wn={=gEWx  b/7,F!l@ {Km΅@$ @@G1tӴ̀::+VpN-=ھ{^zJ @ \  4nlZ^zرGoҤIR`AމzV _|EId=qDD?úpEt.OĉSjBKcС)#׮OkItZnm5<곮<<t}m`jԤ }եw#sҼys*4@VZk* fH*UMF #!9< ڜ @@9ħ>)蝦ZWYf*.5ݼN$͞=W'tIӢN@S/_ަ'>J nѧ5wtR'7n,֭3ڵk'F}]ѣG%O<^N={/h0@رҭ[7tRm 4Ѐ'Y]y}kϾM'piɎ?!C9y}u\]`޼yfb]ߙ={v9q#u#G Ѳ  Zy`lߧr2NK]s Lp R\#>dɒFHߚc!mg[wߟ - >T   @ CN9O+A=fܸq*4 @zDK`%-Μ9#.\0Ѭ}/Z_ޜJ')W\d:\N=֟wfoQ9xď?Ġ DŰB<%k֬vR\4>Ӓ)S&eKVAv{ qV 'ɂ4t 8K VAZDMFO6nh2^q ow"&@@@SM6˗/-[֤O6]wޱPhi7MNR p[n,1&T o94}X'M5#>햶|ryСC:uj9u &! o^Mf(Pc#+#]駟Νk36<ĚAKwA'9< @,=C .>kkwOJt"' շ!6   T 4h u͠u|'ҹsg"EȡO hɄ K.KGc) ShШjHYY43gZnFfd˖shf }R\3^hMPc¦HFNI# 4:uj/W_}eB*c~GRi&I.]N ^{o|1|qV΁    O=DlV5R|yр J׮]c&f$ۢA'NsJ(8Y>^TR_8=_1c|ᇞ}?qjԨ(A8~9s;OFtYP@I,YbϨ44I&RV-? s%৿fj=zB f.C0Cly &t7     ' &YfĉEkzksɮ)oݺ+ѧ][hadΜYWƍrq;@أMvڲo>9pӇ)SvKժUeΝT^=3F <҉yZc7+a7҉ʠ?ѣGFQYu̘1h /@@C@@@2-yfD3 txԩSΪ.u2aÆ KՉ> j[@ҿvd)Lwؽ+X>-,Y23+WL qf 9W.{KJ,)l<=:Gma:th ċfQXzԩS'b9sF4KPHnݺҡC6@hÛƐ@@@@7 <&-}2es, k5k͛7n:xqj6i$;(L(}M'|I&5)ԝw6s.o &rk֬ 4lc~, EKm۶& U\9N̑#UK.r`H>L6-zV  {9    oI$}['E>|lIm)%HԻ|z9؛X-[?`+V,ԩk޽č\~O-[4w\裏y4S[oe_K/$Yf5{7{HV@K 2Dr(I\dǎRpa/XdBQ"Y'>F?1b@@@@:;vX% X-)$sJ*rY_8qdRBW^ .0qDRrΙ3}M'bZnE'hRKJoPB&:^П={ǚ:u\~ݾ1 1пK,)۷olٲutK@gnB~4@%@@C\     ͚5;w$g0K.ŋѣGʕ+&3É'f}RC׫Wٝ4 E47ͻ=Nzz ^?|nݺe&M J)8DZ!YdfLOC؋UaL@͓r    tbAQO?dʕ+'G%[_@(קeɒEj֬A+&eʔ֌  zviР9sR3,ZȖCy8Np'xB:vh7|r=N֮]+=4o\L40_~to)mlc[n 1'a„&EKW@ rE    +'O.V2AU fxItW A!@bҤIԋ~;wv^@ƌiӦL:#GR)J-[7&MF;;fz 4k׮AW@  n&   UVZwSO=e>IBi'|y/^4fʔ+A@7o={:8q}Mw{'`M6t W_}U}Y*VjUQO? 2|`WfΜl=+@ ~r5    ktstƍ)i#p1o퀵d-2d :I봀)P2\K-mШQ#{yf Xdh ~V\ٞ, "PG{ɑ#3Fn߾mEcqv%˗o& 64%CΝ;])h _B@@@@U'O9s1}g/>KsN,Ξ= "R;3O';'Ѭ&gϞu^i٦Mٿ}ԩSXb5RͼC裏f .:f0adϞ]{=z]̙e?H z:Gwߕ/RΝ;E=3B6iXDKm@@@@ dʔI>Qm6)Y$O&ܹsbdɤYf.m1˗/K"Ed֭aaƅ {4}-k:Q+лwo;9s戦ُM:>s ݺu2JJF)Ds%v:F@ A7oh @ !fg@@@p@ʔ)eݺuҶm[;'N>R[ĉotDiҤ1i +V-Ge ow\1¾M63hYNӚ~N *$WGhy&-MtUǏ :T @ s5W   J ^x2m4ɉر\"5k-KAsСCf@bŒN:gp1d$8|3Zg3P}]iDn޼i_GΝ;\rgsFWMg2|6KItO@34AFz  @  !p.@@@p[oeje;ODNbC߿L8^fȝ;}Mw,Yf':EÇuNG֭+Y$IYlh:t z]tIfΜAzqD?ԫWdٲe=MiƊG tmtb@߾}m^={烆 hYE@@@\):QY7{ܸqRvmv+SuyYd.]> +VL'իW'知B (7o.ǎ3t>;t@ɥ]vvTcƌ5h9uhٚ ]Qj{޼yDh1W gΜ4L>]ҧOsAr@  o>   I@kٳGtik׮5O-j,ȝ;w5N:Q3j4իeРA f}_;wߕUV9/ޓt)ĉc4Fti/^^z /SEDi&џ hS7|c͠ 3hF@@@\)O_n޼Y^z%;~A/.vF@K~L2Şcǎvծ%qAtiZ~Bi,ZH 6i `_qiԨQl_;ׯ_77ٳgc۷͛W.]j~+Wή HRJ `b@@@@7 $JH y/)_hrv)O6'/^tgz@L gΝ;t|ɜ9s;wvܱc}O2~xS:Bj<eΜYf̘!67t]@|+@@o9    @(%?y>ۼys8p`(. %IDҥKd|駢@4͛"E Y|轣@ѢE3[eiRs5%wn>~TdrqyWD@G H6    D@˖-eÆ :uj;_4i"nݲD@,Yo4 ȑäY8qd…xCO>v/n^Z *$-Z'Oڡ&NXy__ mt@@ 8!   D@ҥeϞ=֝Z*  z=!!ЫW/So9֐!CrK~&PfMɓ'f<3f`RLU)# VtEN8!|$M@ bb'@@@@Ȗ-h-*Ua޽[/.xW@dʔKӧm \v\7Fz۾a֬Yr%-~Aԩ#xm6;,fرc2aI6F@ ab_@@@@(H,h:N:1NzZx !rq]v̙SRLi_O-ܼyS&M 9uꔴnZ ,(+W 4ի?ٳ @@ 4U@@@@L nܸfROӯljnjڵk qEٸ hKVdIҏ$H ]vWn߾m_GE… ҳgOɕ+̞=[ӞyٴiY:8Y"  !"z@@@@ JtBmժUNC:v(݋1EדwVwڳ>tYf 0W_ɜ9s䊮_.ȑC>cs3<#˖-;vHrz:  7h"@@@@@jd5kV{S;r]G'b4DW~lbt[ UTsG:/#} FKG 8P4#g̙3Rn]g5K@@4x!   R gTN[~y} g<Yfl_\xѾzر;&W Ҭ7 "w&pZԩE+?n-9Y"   @@@@O>lܸQ7onϭ~%JT$x44 /_^2fhv]޽;w}eDaճ5jGFgʕR`Aiٲ%ߴiS;K9_~tSwv7oζmۤtRNŋ']v5 36iҤv@@ hla    3}zX'r&LhΩ۴i#oO#D/_ωԺu>Ms@Ϟ=%gΜvp|2l0ɞ= 2Dnܸa/RJ*, ڵkK2edF4O?$Ǐ͆CC@|-@@9    @ 4nX6m$iӦ>|4lؐV$t}ʅ Ή'qƅ%ZrBg+W&cÄ D}h'ЧO;ŋ˩St(TZ*!jԨ!0oeh/@@_ Km΅    %JȞ={@|K.5O vG hӧ}ɒ%}MǝǷ,ɓ'=tMr-gϖmt/P^=y@߿/cǎ נ5HGs0gΜ@eYzu:oB@ @@9    t]'kժeo>)V|vwǎm {ƭQߖ_Ք\ C'[n->h?bǎ-z6mhжk׮M Ͷ♩#_||rSrBKO@@;8@@@@.OI:I@PlY/~>?ƍ~fȑ#rJ.-bŲ@)dСr ҥċ^q x~z{4%UTfׯ_)S`5p?6%G4ӊiYfYfɡCN:j  khpͭ`     !O5Gf/nܸ7oޔ^zI cԩS+Ve˖JA3qDyͤgd^ݻw寿~Iv)k֬se?GӥK'&L0-Z^8モʕ+ˋ/(wvVtfԩ{ghPmkGKhi\rIϞ=EKM8-u2fyhժUσK@@ a ".Ppa9p9PBd?(G@@@@  lذ2xi<Џ?~4-;0i$ѴA'B9s4='A5 _ڵkK{$Ip>}D3@ lCz!cǎa3F/ %Hhp]`    nЧkժ%֌_tMn7D-&… eŊ21hYUJݥZj&(A')4ݳ ,rv:}:]1hЌ ۷ÇƉGnݺ%NFk;oʺiVǎM˓O>]ن hp-_^1!    5s]vIrn*%Jcǎuٻw+RX1>]hQiڴivn]VW.ܐ*U*T֭ٳ#xرCcD.]`|M6QT))^=ɓ%iҤO'JH~~}^@K{hgr,3xeΞ=+~td/ iZZD=7n@@W4#dl    xY@'u^3g8qByYhTTg]f4qMJh C֭~r ~-CnZAKI/^<>} 4qF2ZFCdʔIڴi#ڵ̙3L^ei֬9s2d̘Q͛g&J: ifͺiDmY@[1p@@@@o曒+W.i޼ܼySݻ':u2)GIyo54kq;W_ۛ@oO&}:dɒrI3 GV\)ӦM3ۜq2h Sjժ&kCZb|Q;%4w tU4@_ &,g-EIhp#_|Y   {.@@@@ & ԫWOm&Ǝ+u T'|b3h醑#GR fO:,XL˗O ( _K D98M'׮]+ח,Y}7gsZ={d0` f,%} !Smذ|y~) RZ@ ܰw^y+ Sp6@@@@%Ppaٳg)R^ի^SNunhjQF!v9F}v50۷)O7iD$H`O|˒)S&?lGlRf@Oɾ}hѢrx,r.ubm֮ D'Z@@@@", /~c?^ʗ// ,ֹ}1cǎIn"u4kCŊerSZ!o޼/c+W.PWCK6;'OIҤIeFkr8jN?j# ~/@@B.@@@@: f͚%7rM6ۧf̘a&`7qARJeJ+9rDn*-Z`6m$͚5ҡet_mZN)\Nzw<ҥKһw@#߿|.\dx Uzg.@@@@ jJ֯_/:!o ppKx 6]v>}zЬ}2~x)P˗eر/_>)S̞=[>@@ ]l讋9~!@@_&   4oP'|uUVv]X;:Ow'i% '!k]^*>)-?]ĉm=f)Y]V~GC4@Rիkזĉ#رCXt8_uEK迉9~2 piѱt믿r7n0dɒ%RZ{D6hqZ5d֬Y&MgK@APb*bR s   X`I)Y֯_?E@Xmf&uZrLipN5NkhfnI{_M3$d˖fX7n |\+V̔hР)ٴTLFjY͠YT5k&4>|#?cD@ & vk|k@@@@@ M47Jڴi!^zI4H fҥy`(@ҥҷo_Ѳj)Ss犖D xmZlٲYΜ9#6l0,RN09נ?fBe94#C})CeZh!I& ֫W A3 h-9wPTPpH"F̙/}-RhQрüyd̙8qbg5K@@P $vA@@@@ <:i=4U2edٲe=g{}>w܏ݟGŋ&k3I7o^3fLxe/^<g_e>M6$I:s>ϕ9r-!~4(7zhi۶ܿ?4,=zn+Q `R  @h@@@@@ ر#Ivmn_1"q~wy7e…v>AOC 2 Ж AY`2#֭khpŋQFR4_~1eUj֬i+va֭s\n@IDATٳk۷ou.]2٭[7.VXƶm${t@@&@@Cؼ@@@@p hPڵk%y<}J6mݻsNiܸS#G;wҥK' 9y7n^ $J|rɘ1=~/\b_&Mٸq+Wmҥ&,XPVXaO^֭['CuM`@@hp@@@@WRJ&pAO{nRD'{ݻlIĉEM&瘵j05kVꫯlpϭ[D+c] DݴjԨ!ju  @h!G@@@@@ yݻwK2e{.\`р-SH miӦ G >}|+&|ˤIpZ˖-M8\`W^yE͛gTbEYz$IĮ  ]@@@@@ d˖-.]:#FK/$7o޴ ^  B˙̟?_ŋCz}yٸqh\v=iڴ,Xrʲj*   x_rD@@@@,PhQٳg,XоW+WNΞ=kA <6A3{'N(9rp^FW_ TrE 1qƲh"kTZ5Ybͪb7A@ ^'    O s̲m6Y=޽{DrAaرc-͛7-ZQqY&uT`F9Fl2I0%  @$ @@@@ $ID/_.=zo=}.]ڤ+  G4fٳˤIBNr@'z _~ ޗ/ܹ#Z FN],]T$Hb  4D20G@@@@ qđcǚgk~ԫWϬ? tQN3!A@L(    ] VXʬY$~)10lذwDKZj%>4ה?~=z_^_"EO?cu\ržFڵkRZ5ٺu=ܫ*3gvt@@h*@@C4\    @ЉKT̅$Чܹ/wޕ'Oʖ-[W^ءMy,X,~I2 ѡ{3KիWJ*}vҮ];>}: V  q~@@@@#PLٵkԬYS?n1g3l2 a h(>}Z~w ?w&o< A?#\6֭+;wEDY<6 {#W &`E   =     .İn:ԩI_dɒxbXb˛"O`ҤIҭ[+U mڴ%Kyda=kO>[D._,*Uc=7n}M@@]4~0@@@@%/^<6m̙S'Z@FVI߶mp7E)SB̐.]d8`W^Gt@@'@@ #B@@@@ }5e&^yyܽ{Wڵk'?yr_Eޟ7|$YdR~}Y$Ibiπs΅_z% fӧ92   7Μ@@@@ ͛N:[-C1w\ X8Q:sNYtիҺuk)W\okӥKgtΟ?/6moѠaÆt@@+۽Ccd     ^Eݻ`:y^lYȓ`t"$Z"ヒ15s֭[P/lbՌ3X:  hp-b    O@Kl۶Mj֬iKbv ]V .l248gVZ9/Yqı̠6π !  s) {ACFJ4F ;!L#32a4f;3vl42L9$SrLcdcBCbTT֪:\We @ZUVq~4:@޽sJ ,[,;B䣏>ӟnh׮]hH{&M4 kM @@nr !@ @ P7\sM_ .;,ꪺyhu{O=<կ~izh"nb6[W쵍~U[?9sfv2 + @hZU* @ @H4|_ǧ~iĀN;-^{ꫣvX Rp! ^zKA/Т]n~/WGy$9Sҽ}ݳ}VVXLFhӦͪ'W'MQ6-۷]wݵ"@ȫ@i5U @ @ "pǓO>;vwuAH! 6,~F뮻gw}7 3$1rtlM?)h-]tQ$aG1}taVڷr!N`˿Kپ2f̘oV͝7o^aDѶm)2gΜ}R͛7cƭ-[\5ԓW4$vbʔ)&2 + @h(R( @ @ 'Fx?Avә3gFϞ= d;+|%˞{ 4Y #0̞=0-G~Yf _|E<1dȐW7"gҤIyi4É'R@uzTu]/bv@CFa P6M˦RV@0 W\ݺu+n$@ @#4bҥK,<%,зoCQoB GqD6Db„ vۭRWQaСij`?~|痜_G7pCnݺ<5 T7Bx뭷skSO=[nݻw_ @O WQb邏N=# @  @ @KQF_"nlŋG6VmuK.$>c6l{ 3\o^)R$pgFFrkof ϞtDu=d'X!@ȥ@C.EQ @ @Wc-M6X".H,YRzIW\qE#ۮJ{>}/_0ȑ# OL6-~VΫF`u~KwU 4n @\L9Q.=N @ @u,gC=qow}wu\F7ވ9sFHSOlevϲaVh}ʱ 檁e˖ ņ?䓱xf_O?m 4dV @e' Pv]` @ @+}/_ovك_G}Y\VN7=zr)չrW^ѺuC 3L:u>-ZT؟HX @So^_{l;<; [;{Oӷf?DfV @ @ѣGL6-/r?OメC>*^}U XShhڴi|ߌKNׯ*B7E( PY7G]! Vu )'|n @ @&W]uU\{Q .^\vY}7"%+UX 4t5~Bič;~CɦMSm @jh y䑸ˢYf)oVs6bҥ~+ @ @''MSOlƥ_8?/L[_>hV&2ܮT 4"iINN:)> Xiٲeq' @@ 4a4gӦMw=++_*z_V @ @*OSON;5뮋C=4>l_V,XSLJh(rRuʉTdutM㬳Ϊ} @e$ PFRsxbС%%KѣG=zt, @ @)Э[Yzpgm̕t޽{ޘ%yv-V7BC lڴi K.0JÇ~Ύ[!@(OUfW_'Nm6oqg{wV @ @*K}sHFիW<3]sذaﮊG[jUS itشl/ǝwf͊y? En:-R @4sk8CW^A;y޽{7d  @ @h޼y??k|ijo=W+F4 Fq9#⢋.*ṉʁw1.( @`}Gϵ m6;vll2;g{qGV @ @*GQF_"nh֬Yaiϣ>[z=ĿۿeMbz뭑_`MWB @hX_AׯVO^z0`Փh >`  @ @*H aˤIM6VX"^%KyK?1bDs1QSNqFIR O$@ԕ@C]ɺoAK.O~xܹs#MO1th @ @TӦM]w5k]8ދ:+:v\rI|l?9ڷo_u:bzD)'nK #uFҴiœSNΝ;4k- W @ @@OǷM`zZo}WfϞC 4 W\-n[Gzk׮>+!иqW o$@l(Pwu~/ 'PQ @ @*U`x ~ӟfM|7#}_4ylߺ _W뮋/"Mv◿e[ѧOlXy! @@vZj7tSu]k} @ @&l7pC\veѨQB|IaZҴ]4iϞ=o/2Ebёw^l1+%r %@@ w} 5O^+)0lذZy6= @ :?<8p`{~8dܸqѢE]Z?M[Q\k>}z>W @C  @# а>z]o4aq @ @{g|;/`ԴTswf /^j#4p @T@Cwn O>ت?TG82C۶m㑞A @Xgv!x8/矏^zc=\㽫а;\O`ɒ%MӒڎQ<' @@y 4w}N̚5+s΍6mDO>Ѯ]m_ӦMcС^)S@CB @Ȼ@V{3<3Bs̉8 n´kC@C.]Vwe(B GydL81㏏w1۶B P ǹlapĈT[_ƍw1|8蠃=N @ @H=:ve8S/ ƠA+O?}~ꫯfАQJVc)]In @44fN:JHS+:vݺuDZ)W_}KZ|y<ѯ_7|slkA @ @8S"~i~FL'Q1 'P=4{Q<䳌RwqqwfH4j&M}V @0ګH`ҤIq9 /P iʅK/ /07{ 5L0!Ҽ @ @8㩧|;1{BCnjoV-"H.j(n,O+VO~T#zqY| @T@onhQF!0Czg}#G?<>B \|T+釗t_  @ @@e @MoЇz(?~|mC )n,SfH#n뮻Yf]>  @@Cmn 3y晑汫͒~,qX4{w!Fa9sfw}q%D߾}W3" ;h!@ @|ǣ>GuTW_}5ŋ n馛VW`ذa1vجᦛnB ^sw^㎑ ե"v[<ٵ͛74`J><;H bq-V[m]VҾd  @ @H!G+/5*5۶mW>d8㌸k{]c6Y!@h ש)%_d6i$=ܘ?~sł V0 wqE,.)]}gGc=6T)_uI#8X @ @@ .iLoU&}wd)oѣFiF&L-[Y!@h ת)+.)F\KKFQH#43y0`@)*N8߿vlM+:t믿䔉'ƢEJ @ @lG1iҤz MzՀ4꾩˗/ ?U}'پ?8wwU+OK MM<9f6 @>(_8h ki&fΜYؚ5kVm.Yٳg:Ob @ @ FI&6՜{bŊH#{7n4hPXSC)xP T !T]OS!gC-;S!̰o[ @@ 4T`Eo/R֯z,]46dZ=*g~?ZYb k׮U @ @ \sΉw1?XdI|?̓B-*Rj _HؐaLw}|e}oz @ hPmݲ[0Ø1cjM7T6M;oGqjZI?l]s5%%6 @ @GӨ ;SO=}o~hԨFJ߉fŶlWЖ-[[lQ-ܲzn @% %6V'^{: ?M:k.#+@ēO>=y;gV @ @$>}G0`@!Ȑ]q;ĭniU%HH-[ϫwtpAu5jchg @T@Cm#^|},X={6AUY!/ii~D  @ @ȞO?tzwYwqG{W:Aq}u4Ri>U)\кu뒀B+WS @&/)"F73?Rx߿u]YxqL4)F>hm^ @ @m]4 7xH˽_0ܐ[%pQGń b…"N @ @hժUw}q) 7ܰ^mLa*.p(Ҿf͚W .&@ bGǍW;u^a:uj C )*֣| @ @4 Mرck׮.$B]] !M`!@ @߼;N=Ԩ:`׻m;w.gXFiD믿>~Vm @ @@8H, @@*Wy8c̙1bĈm e]^ 4T]zӧOfb @ @ @ @ 몉K\tEG|8_~vUK @ @ @9hqSib4 @ @ @r;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @v@IDAT @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @@;@ @ @ @N@!w]  @ @ @ @)SwމYfśos΍6mDO>Ѯ]Yg @ @ @ @Fh؈ ӧO#F#Gy?.uW^C=pu @ @ @ @ʪ6~)pgƲejUO/[4{w!Fa9sfw}q%D߾}W3SNYe @ @ @ Py קu֢W_}5;j;F '4klcǎn-~X.iӦnݺ .ɓ'-[mUvMZI}6 @ @ @ @*OEÆ /"&Msύo=\,XRw}VXw\Fd8#cu˱iۗFp @ @ @ @@e 4Tvn֥£>/҈ ^zi( i={#< ί:E 'ώiCqח2qXhQ> @ @ @ PY ՟u֚qƕpG߾}W-Z^vH'7n8.^W݁ACK,)SdV @ @ @ @*OE>lvJ խj* Rrsα[F3f̨e!@ @ @ @2h(ӎ̙=SNѺul{M+'tRݻlvc=,9?,ٶA @ @ @% PYYg;wnv.]d5t1ťAϕ`! @ @ @ P ةuѤm6mkXILYfWs%Q", @ @ @ P ۷ekv_=.]m״>6lS8^jϜ9d׮]Km @ @ @ @@e 4TVYkvm)0f̘ln)XhQ<ӱ|.)9bŊkJ%6 @ @ @ @*?5{Wɽ?xwKմѢEH#+4nv] D<7o;sm @ @ @*O`~\yעZ 80,X={'kZaȑq)\|iE&MJ @ @ @ @h֤QF]r4ĀbРAq7Ɯ9sJŋ<09HS\v!^I @ @ @*дBۥYu ЧO9sf1"vmc~ʁ^z?z  @ @ @ @|O*͛.]tQ+i]z˗/>8mr]xK,y @ @ @J@d}7tSOg}6:X`FkϢE6ڳ= @ @ @+`ʉt=jƏQ VS] @ @ @('U1;F:Ma!@ @ @ @@ 4kϩ;]volٲzWuʎΝ;Hq @ @ @X@5t@] ;1k֬x7cܹѦMСC'ڵkW׏7n\Fy< @ @ @! PʞcĈr,HA޽{㠃;  @ @ @ @r*oeSN-x㍘7o^t1u?m۶֐Eꫫ=Vܹ|xǣ_~1hРc7/I @ @ @. P\W͛4iRs9 /T4СCK/&Md\x5g{)0a„aV>l @ @ @*PqIX`ԨQq!6̐gȑ#?P_|:U+ġZ:E @ @ @ PV e]f83cٲe*fqέn޻rH0̜93ᄌKo߾ƌq) @ @ @<:kѫw^q#5kcm?pvyqeŴibqaEnbqɓ[n*&}?|> @ @ @ Py קu֢aÆ_|ݿI&qߎ{.,XP)y+V;.gqI#2}ّc4DKNI#8X @ @ @ @*7XR`GiąK/d4BCϞ=GdWNgִҡCKN8qb,Zd  @ @ @,:k͸qJFX8ӣo߾}^-o/ ;7n_~jAb١%KĔ)Sm+ @ @ @ Py קuҢg}6o %\xVZjC )9ܹszKfh3f2 @ @ @ @@ 4iws٩ShݺuN:pKk{l @ @ @ @h̝;7w.]V:viR D\`AO @ @ @@ ԺhҶnݶh 5i&ˬYk9{( @ @ @\ ڲ]w5믿K.ͶkZg}bm)ktzgΜYk׮%6 @ @ @ @*?5[vf3fL]M7~,Z(~X|yM_bE\s5%v}m @ @ @ PY ՟u֚ګ~%jhѢE7^."|͛7w9۶B @ @ @'vYkQ-UC ,={ĉky?-0r8SJ.>ӢI&%l @ @ @ @@e 4TVYkҨ G.Fb1hРcΜ9%ucqǁsN).;Ç/n$@ @ @ @ hZҬ:ӧO <8㎒{q5j( -[,9^ۍ4"QG&L V{٨QN @ @ @ȥr--jܸqq'V[`N+lSN]maȐ!@E @ @ @*J@ӬY;vl\wu&<{%ѹsU.K#> ŵ^1; @ @ @ @2*_U'|r̜93F[y{z?w@C^bp @ @ @ P>M˧TM`]v.W^4z.=z˗n}oz @ @ @(C2sb-SN O'@L{|ᇅݻǷ- ߅=o 4s߬xdX[%N;]?lt'|2z]cC!?1~ٳgL6-bqg$?ӟDr"pYgW\QfСqW2e PL9PzZ;  @ @ @ @@ 4Qg) @ @ @4I @ @ @H@:K @ @ @ @44N @ @ @ PF eYJ%@ @ @ @ E@v @ @ @ @2h(R* @ @ @h(  @ @ @@CuR  @ @ @ @@Chh(= @ @ @ @ʨJ @ @ @@CCi$@ @ @ @e$ PFT;\ I@ H kHX 0"82/3( hP#A@ `"k 6%lj:'UU׽]+Nusw @ @ @ @]4HN @ @ @ Dht @ @ @viI @ @ @H@@C  @ @ @ @v.#:  @ @ @ @@ hhU @ @ @ .e]' @ @ @h" M4XJ @ @ @E@@C$@ @ @ @M$ KW  @ @ @ @@hhv @ @ @ @44`* @ @ @h 2Ү @ @ @4&,]%@@>^Xc5 @@FGC hʨgBkf!W+e @`y9X1Ty9W @HNO},0mڴp 7+7t (rK2eJ8èQT%@>4iR1cF8#ȑ#NG"0q0gΜpGZjQNXCwy'|n8СCNEiA @ @ @,`ˉ @ @ @ P_@@C}#- @ @ @ @4  @ @ @ @iA @ @ @, dpoG @ @ @PH  @ @ @ @d %{; @ @ @ @FZ @ @ @ @% h( @ @ @ @47҂ @ @ @(Y@@Cގ @ @ @/  @ @ @ @@Jv @ @ @ @@} @ @ @ @JP2#@ @ @ @ ho @ @ @ P @ @ @ P_@@C}#- @ @ @ @4  @ @ @ @iA @ @ @, dpoG @ @ @PH  @ @ @ @d %{; @ @ @ @FZ @ @ @ @% h( @ @ @ @47҂ @ @ @(Y@@Cގ @ @ @/  @ @ @ @@Jv @ @ @ @@} @ @ @ @JP2#@ @ @ @ ho @ @ @ P @ @ @ P_@@C}#- @ @ @ @4  @ @ @ @iA @ @ @, dpoG @ @ @PH  @ @ @ @d %{; @ @ @ @M @0ua̙a֬YaȑaԧK/]‰'X}+s1ղ M9z?pߐ!C:ƍW&O"@@ =G?s/=P;>UW]5;6am  jqwx~wLu4$@ 4GG/L4)L6-< :vi0xg?oK ;A;Rv!p ''u|ԨQkݶ W^ lAx'8cù[- @]ʞc`q^9& @ʞ_}pgs9'k=r3&L0!l="ZA` ]vY\I'<̺.3n4 @@&9ё"޺2ni[lQcWH@\rI)3Kyꩧnvq0{~#8/oh2D>:!we;H(s=ӟToO? ?vapϒD'PgB]3 @w\ NM0Cl _ҭ+"а@馛!^~.']i¶n[YJ|%~W?OC' @ʜ{>?}喫|^".i^׿V WR2ZR9: y.K.d+x6r $?*)Ǡ;K`BW nj-_:n_SI@hhvd&0wܰw"{nJ4j*.JS\FZHD\A"@@ =GqaaÆ~$iSL~&L>?: _nje@st\&6k֬6lSYQ''3f_ A mO;pu J#F+"צxwL H`H?aAַJO<A16}_vE@ tDGI 0gw|u'>x ,b;޼z Nױ,XoN ZB9;?dȐ;v<@]ˉ'ѬpѣwVX  Ьe(εX)mo@/ܾZkx G}t.ꑥτ;E_|m'zk{w]Z"@}1#J([ >uv<[)el;nDW^yeMƋqnA({K'L8ׯzU ~c9z|K/-) @Uʞ;n3<\"l>-᪫J#<~ @oW_}u8 )C=PLH[$0st+ϛ7J5|ʊmQng]) CxA"@@ 9GG;S@O~PWG?XbB#DO 4qZ5τhj 1{q= &)ST]?ᦛnw hQ -:.F]`\]!.:4 nƐ.{k;7jC(s7n\8þ[N#H4^!nx7 u h2{,i'>[E;{hjMzO"^fmlIe~&? @eM<FGǃǏ_Y!;+6nAQxZZ@@CK#@ W%6z뭵Uݖckkf_Ν8y*]vYXz饫u2hG2o|-οqoɩS.=S8.>0bĈBZA9vuF5̸ N.@68#C LC ]tQ4hPgUL#^$@@ ,X*y4$ !.  7Ї =ӺDvnq!C9$-*IOo:V#@st#qkiӦX9*H-a/P]O<qY/,#s1a 6Hz̗cGHX9(l VߋcoK/S @@N   P׿;av q' /J++? ^{mY{=TH 7>;⋅veBY$P=gΜۘ1c FUhW\{ u h'/O,\r P={+ j4@_ӧO/r|X Je[/-\߹>0k֬1 |WOy÷pV; OuQ}#]wglכor|04! оh߱wd"ޫ <8-#&OP0C<7>1("D 9Gw߃`_Wqڭ  B=G׮W6[qF1~PCI' Hߟ 9 @Ij_Z!CwܱK0C\g7[oux_80eʔx `C|2a7\sMz=^bSN Y}%qĉaРA:@sߵw8uYq ƫ:,첅z@st ݭ[ Q҆vOƭ7W<Ӫe|&4) @j~7C\%8M_W*[*w/;#zᥗ^i:U|+eh?[Nߘb2KxŽu+6`V̙3Cӟ4\uUa]vr"Fw߭V_xᅖ$j @(P]|UЩڥa ?إ^ZM97(-C-' c&pWgyp'pBX؀x> V @@ OWgqF8Ç)>t7v_2L0!}х{l~È# h m2.b??ᄏбT%\>Oc!%|ɕ/tϯ[o=#/gY>裝1O,C({;s{vi|'x]U @ʜi .NzÇPm 2l c#G ~B_c I @ r)p# 'xb]es9նs̩<~Z'C@ ꘨X\+%@B Yw呇.)IDAT~>~-X;2kܼ++;{NƧf͚ƌS}̤IY qr-W.VV|-Fv#Z @`qY `;蠃_ . uQ]U @ 'fi{sgquFӌ3_8n;CN!Wsm0>i_wyiUC241  G8Gm$VǵiԨQ K/tKݖyYg½W_=} _rٳw{ SM֛CmS潕n;%4ݱq>~bַ ؖbky@@ 7U$_.(d} 7T /O8 /Tqae @(P]|z8yҴKaV @ʞk[-LnZ[s;"p5k}CF e&47m  l}9G<8{i 7[],9sf4 @%"-.[{:bĈZn]w]brsO?]7\}=fR]zhnA /r-]װ[) @ʚ{cne]‹/Xhˇ8@$PjKO?{eYkHfO.c*-h'@@wq)n\nim2-.  >Z??ʽ-=}kSzW^yn^hfA 4&@)y4^@p~UVٙ7o^Zk: 6&Pzk+C%X"Kp W_}ukXԀ?\ @ƍ sk휝)JO:^#@`uL<Gm؆^MҬ]NДæ *EIK-THWOnOޜ?n1o1ZP @Za>I'el^ziX|Ż Ќst ; ;sCSN-G>R(+ @` b~qڇ/G;{+ݩ#@une*?p!5jT:cWH@f1t^͚5o$3soYq~ϯ|߬뮻v+݌-p( eQ} 'xbN=p駇AuyMI9z̘1Uўx*~Νf̘Q=>f> heVbʲ?jA _stw 7' mC cY@@C>^%@@ {ow}wi5XZכL<6?S*.߸(K8^tE$䗾e Њeѵv/QGU w@@; {zNg9[lΪN4P<.n!$ @]j,`_<1 2f9zayWSN9ZMEY^hر:C`p{\$@@>/RL#=m$-+1bD.Hz9g x`ɸb@ˋ@џg q+P;8˟gy  @eq^z{= i' @@f5Gg} W?1?.To~_Ԟ--  vmYgB&L~z*{駟.49#!)( @Ao/zzA?P@v9:>YW*K 'K vXӟTxѧ + @z?7߼P^@|&,l_Gs׻_p|Džo;ҰaúI( Zzx]9 Ġ3<еJî6=>G?QY;O.) @@eѓ'O_V\qŰ{S.ҿx@"@@+ =Gw]x!nԙwm&L67K o}h u he3gy.q3aa8&Пstָ\P^x!lf ?#6<@د}kaԨQ:G`PǏhr])QthM6 rHG?V[m/ӧ{キܹs ,[nxdmgB{E  em/[o~9 @` ʚk<ĉӪʒ1pam [oux'+Aj&MrvWE D;. ;cnQ (u,r(c}.?/\+{ŒK.x/K}\5->bh 3֮2q]MmWck|+I@C_:$PK/[X4 0eѵ믇O[o}nyo:C]) h5K/4ĭt} 70Z@|&,RLLʘ?=n!3fw!`ˉGWA@ HԸD):nܸ` wh123f!@@9eѵW7Rwmڗz,=:ħm5# hQg}˕y/@|&e%P-ntW~!4Hqe iK54渺*H` 8J+cx3o|\azlEX44,89GwpׇM7ݴ&պgz Ў?k]v~(3_.I  P@st >};σ?_2k-'d]&# 9sfx+㗶k <h1st !@ʞ0y0{0gΜˇcVK-TK4@ٟ dhy f 1^ Ze%-Q. @ Аj @ @ @ @Yr"a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@ @ @ @ @R < @ @ @ ,A' @ @ @ @T@@C!O @ @ @d! !a  @ @ @ @ Аj @ @ @ @Yhbt @ @ @H4 @ @ @ @@KIENDB`wkutils/man/wkb_meta.Rd0000644000176200001440000000335714163201260014612 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/meta.R \name{wkb_meta} \alias{wkb_meta} \alias{wkt_meta} \alias{wkt_streamer_meta} \alias{wk_geometry_type} \alias{wk_geometry_type_id} \title{Extract meta information} \usage{ wkb_meta(wkb, recursive = FALSE) wkt_meta(wkt, recursive = FALSE) wkt_streamer_meta(wkt, recursive = FALSE) wk_geometry_type(type_id) wk_geometry_type_id(type) } \arguments{ \item{wkb}{A \code{list()} of \code{\link[=raw]{raw()}} vectors, such as that returned by \code{sf::st_as_binary()}.} \item{recursive}{Pass \code{TRUE} to recurse into multi-geometries and collections to extract meta of sub-geometries} \item{wkt}{A character vector containing well-known text.} \item{type_id}{An integer version of the geometry type} \item{type}{A string version of the geometry type (e.g., point, linestring, polygon, multipoint, multilinestring, multipolygon, geometrycollection)} } \value{ A data.frame with columns: \itemize{ \item \code{feature_id}: The index of the top-level feature \item \code{nest_id}: The recursion level (if feature is a geometry collection) \item \code{part_id}: The part index (if nested within a multi-geometry or collection) \item \code{type_id}: The type identifier (see \code{\link[=wk_geometry_type]{wk_geometry_type()}}) \item \code{size}: For points and linestrings the number of points, for polygons the number of rings, and for mutlti-geometries and collection types, the number of child geometries. \item \code{srid}: The spatial reference identifier as an integer } } \description{ Extract meta information } \examples{ wkt_meta("POINT (30 10)") wkt_meta("GEOMETRYCOLLECTION (POINT (30 10))", recursive = FALSE) wkt_meta("GEOMETRYCOLLECTION (POINT (30 10))", recursive = TRUE) } wkutils/man/coords_point_translate_wkt.Rd0000644000176200001440000000433014320423305020456 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/coords-translate.R \name{coords_point_translate_wkt} \alias{coords_point_translate_wkt} \alias{coords_point_translate_wkb} \alias{coords_linestring_translate_wkt} \alias{coords_linestring_translate_wkb} \alias{coords_polygon_translate_wkt} \alias{coords_polygon_translate_wkb} \title{Parse coordinates into well-known formats} \usage{ coords_point_translate_wkt(x, y, z = NA, m = NA, precision = 16, trim = TRUE) coords_point_translate_wkb( x, y, z = NA, m = NA, endian = wk::wk_platform_endian(), buffer_size = 2048 ) coords_linestring_translate_wkt( x, y, z = NA, m = NA, feature_id = 1L, precision = 16, trim = TRUE ) coords_linestring_translate_wkb( x, y, z = NA, m = NA, feature_id = 1L, endian = wk::wk_platform_endian(), buffer_size = 2048 ) coords_polygon_translate_wkt( x, y, z = NA, m = NA, feature_id = 1L, ring_id = 1L, precision = 16, trim = TRUE ) coords_polygon_translate_wkb( x, y, z = NA, m = NA, feature_id = 1L, ring_id = 1L, endian = wk::wk_platform_endian(), buffer_size = 2048 ) } \arguments{ \item{x, y, z, m}{Vectors of coordinate values} \item{precision}{The rounding precision to use when writing (number of decimal places).} \item{trim}{Trim unnecessary zeroes in the output?} \item{endian}{Force the endian of the resulting WKB.} \item{buffer_size}{The buffer size to use when converting to WKB.} \item{feature_id, ring_id}{Vectors for which a change in sequential values indicates a new feature or ring. Use \code{\link[=factor]{factor()}} to convert from a character vector.} } \value{ \verb{*_translate_wkt()} returns a character vector of well-known text; \verb{*_translate_wkb()} returns a list of raw vectors. } \description{ These functions provide the reverse function of \code{\link[=wkt_coords]{wkt_coords()}} and company: they parse vectors of coordinate values into well-known formats. Polygon rings are automatically closed, as closed rings are assumed or required by many parsers. } \examples{ coords_point_translate_wkt(1:3, 2:4) coords_linestring_translate_wkt(1:5, 2:6, feature_id = c(1, 1, 1, 2, 2)) coords_polygon_translate_wkt(c(0, 10, 0), c(0, 0, 10)) } wkutils/man/wkt_plot.Rd0000644000176200001440000000262114163201260014655 0ustar liggesusers% Generated by roxygen2: do not edit by hand % Please edit documentation in R/plot.R \name{wkt_plot} \alias{wkt_plot} \alias{wkb_plot} \title{Plot well-known geometry vectors} \usage{ wkt_plot( x, ..., asp = 1, bbox = NULL, xlab = "", ylab = "", rule = "evenodd", add = FALSE ) wkb_plot( x, ..., asp = 1, bbox = NULL, xlab = "", ylab = "", rule = "evenodd", add = FALSE ) } \arguments{ \item{x}{A \code{\link[=wkt]{wkt()}} or \code{\link[=wkb]{wkb()}} vector.} \item{...}{Passed to plotting functions for features: \code{\link[graphics:points]{graphics::points()}} for point and multipoint geometries, \code{\link[graphics:lines]{graphics::lines()}} for linestring and multilinestring geometries, and \code{\link[graphics:polypath]{graphics::polypath()}} for polygon and multipolygon geometries.} \item{asp, xlab, ylab}{Passed to \code{\link[graphics:plot.default]{graphics::plot()}}} \item{bbox}{The limits of the plot in the form returned by \code{\link[=wkt_ranges]{wkt_ranges()}}.} \item{rule}{The rule to use for filling polygons (see \code{\link[graphics:polypath]{graphics::polypath()}})} \item{add}{Should a new plot be created, or should \code{x} be added to the existing plot?} } \value{ \code{x}, invisibly } \description{ These plot functions are intended to help debug geometry vectors, and are not intended to be high-performance. } \examples{ wkt_plot("POINT (30 10)") } wkutils/DESCRIPTION0000644000176200001440000000222014361730142013457 0ustar liggesusersPackage: wkutils Title: Utilities for Well-Known Geometry Vectors Version: 0.1.3 Authors@R: person(given = "Dewey", family = "Dunnington", role = c("aut", "cre"), email = "dewey@fishandwhistle.net", comment = c(ORCID = "0000-0002-9415-4582")) Description: Provides extra utilities for well-known formats in the 'wk' package that are outside the scope of that package. Utilities to parse coordinates from data frames, plot well-known geometry vectors, extract meta information from well-known geometry vectors, and calculate bounding boxes are provided. License: MIT + file LICENSE Encoding: UTF-8 RoxygenNote: 7.2.1 Imports: wk (>= 0.3.1), Rcpp, tibble, vctrs LinkingTo: Rcpp Suggests: testthat URL: https://paleolimbot.github.io/wkutils/, https://github.com/paleolimbot/wkutils BugReports: https://github.com/paleolimbot/wkutils/issues NeedsCompilation: yes Packaged: 2023-01-17 02:48:34 UTC; deweydunnington Author: Dewey Dunnington [aut, cre] () Maintainer: Dewey Dunnington Repository: CRAN Date/Publication: 2023-01-18 08:40:02 UTC wkutils/tests/0000755000176200001440000000000014163201260013111 5ustar liggesuserswkutils/tests/testthat/0000755000176200001440000000000014361730142014757 5ustar liggesuserswkutils/tests/testthat/test-grob.R0000644000176200001440000000705214163201260017006 0ustar liggesusers test_that("wkt_grob() works", { expect_is(wkt_grob(character(0)), "gTree") expect_is(wkt_grob("POINT EMPTY"), "gTree") grob_points <- wkt_grob( c("POINT (0.1 0.1)", "POINT (0.9 0.9)"), pch = c(1, 16), col = c("black", "red"), default.units = "npc" ) expect_is(grob_points, "points") expect_equal(grob_points$pch, c(1, 16)) expect_equal(grob_points$gp$col, c("black", "red")) # EMPTY items should be recycled along but not included in grob grob_points_with_empty <- wkt_grob( c("GEOMETRYCOLLECTION EMPTY", "POINT (0.1 0.1)", "POINT (0.9 0.9)"), pch = c(3, 1, 16), col = c("magenta", "black", "red"), default.units = "npc" ) expect_is(grob_points_with_empty, "points") expect_equal(grob_points_with_empty$pch, c(1, 16)) expect_equal(grob_points_with_empty$gp$col, c("black", "red")) # MULTIPOINT has to be handled slightly differently grob_multipoint <- wkt_grob( c("MULTIPOINT EMPTY", "GEOMETRYCOLLECTION EMPTY", "MULTIPOINT ((0.1 0.1), (0.2 0.1))", "POINT (0.5 0.6)"), col = c("magenta", "magenta", "black", "green"), default.units = "npc" ) expect_is(grob_multipoint, "points") expect_identical(grob_multipoint$gp$col, c("black", "black", "green")) grob_lines <- wkt_grob( c("LINESTRING (0.1 0.1, 0.9 0.9)", "LINESTRING (0.1 0.9, 0.9 0.1)"), col = c("black", "red"), default.units = "npc" ) expect_is(grob_lines, "polyline") expect_equal(grob_lines$gp$col, c("black", "red")) # multilines are handled slightly differently because polylineGrob has no # pathId argument grob_multiline <- wkt_grob( c( "MULTILINESTRING ((0.1 0.1, 0.9 0.9), (0.9 0.1, 0.1 0.9))", "MULTILINESTRING ((0.5 0.5, 0.5 1))" ), col = c("green", "blue"), default.units = "npc" ) expect_is(grob_multiline, "polyline") expect_equal(grob_multiline$gp$col, c("green", "green", "blue")) grob_poly <- wkt_grob( c("POLYGON ((0.1 0.1, 0.9 0.1, 0.9 0.9, 0.1 0.9, 0.1 0.1))"), fill = "grey90", default.units = "npc" ) expect_is(grob_poly, "pathgrob") expect_equal(grob_poly$gp$fill, "grey90") grob_mixed <- wkt_grob( c("POLYGON ((0.1 0.1, 0.9 0.1, 0.9 0.9, 0.1 0.9, 0.1 0.1))", "POINT (0.5 0.5)"), col = c("blue", "green"), default.units = "npc" ) expect_is(grob_mixed, "gTree") expect_length(grob_mixed$children, 2) expect_is(grob_mixed$children[[1]], "pathgrob") expect_identical(grob_mixed$children[[1]]$gp$col, "blue") expect_is(grob_mixed$children[[2]], "points") expect_identical(grob_mixed$children[[2]]$gp$col, "green") grob_nested_point <- wkt_grob( c( "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0.2 0.2))))", "LINESTRING EMPTY", "POINT (0.5 0.5)", "GEOMETRYCOLLECTION (POINT (0.7 0.7))" ), col = c("magenta", "black", "cyan", "red"), default.units = "npc" ) expect_is(grob_nested_point, "points") expect_identical(grob_nested_point$gp$col, c("magenta", "cyan", "red")) grid::grid.newpage() grid::grid.draw(grob_points) grid::grid.draw(grob_points_with_empty) grid::grid.draw(grob_multipoint) grid::grid.draw(grob_lines) grid::grid.draw(grob_multiline) grid::grid.draw(grob_poly) grid::grid.draw(grob_mixed) grid::grid.draw(grob_nested_point) }) test_that("wkb_grob() works", { grob_points <- wkb_grob( as_wkb(c("POINT (0.1 0.1)", "POINT (0.9 0.9)")), pch = c(1, 16), col = c("black", "red"), default.units = "npc" ) expect_is(grob_points, "points") expect_equal(grob_points$pch, c(1, 16)) expect_equal(grob_points$gp$col, c("black", "red")) }) wkutils/tests/testthat/test-coords-translate.R0000644000176200001440000000735314163201260021345 0ustar liggesusers test_that("coords_*_translate_wkt() works", { # point expect_identical(coords_point_translate_wkt(double(), double()), character(0)) expect_identical(coords_point_translate_wkt(NA, NA), "POINT EMPTY") expect_identical( coords_point_translate_wkt(1:3, 2:4), c("POINT (1 2)", "POINT (2 3)", "POINT (3 4)") ) expect_identical( coords_point_translate_wkt(1:3, 2:4, 3:5), c("POINT Z (1 2 3)", "POINT Z (2 3 4)", "POINT Z (3 4 5)") ) expect_identical( coords_point_translate_wkt(1:3, 2:4, NA, 3:5), c("POINT M (1 2 3)", "POINT M (2 3 4)", "POINT M (3 4 5)") ) expect_identical( coords_point_translate_wkt(1:3, 2:4, 3:5, 4:6), c("POINT ZM (1 2 3 4)", "POINT ZM (2 3 4 5)", "POINT ZM (3 4 5 6)") ) # linestring expect_identical(coords_linestring_translate_wkt(double(), double()), character(0)) expect_identical( coords_linestring_translate_wkt(1:3, 2:4), "LINESTRING (1 2, 2 3, 3 4)" ) expect_identical( coords_linestring_translate_wkt(1:3, 2:4, 3:5), "LINESTRING Z (1 2 3, 2 3 4, 3 4 5)" ) expect_identical( coords_linestring_translate_wkt(1:3, 2:4, NA, 3:5), "LINESTRING M (1 2 3, 2 3 4, 3 4 5)" ) expect_identical( coords_linestring_translate_wkt(1:3, 2:4, 3:5, 4:6), "LINESTRING ZM (1 2 3 4, 2 3 4 5, 3 4 5 6)" ) expect_identical( coords_linestring_translate_wkt(1:5, 2:6, feature_id = c(1, 1, 1, 2, 2)), c("LINESTRING (1 2, 2 3, 3 4)", "LINESTRING (4 5, 5 6)") ) # polygon expect_identical(coords_polygon_translate_wkt(double(), double()), character(0)) expect_identical( coords_polygon_translate_wkt(c(0, 10, 0), c(0, 0, 10)), "POLYGON ((0 0, 10 0, 0 10, 0 0))" ) expect_identical( coords_polygon_translate_wkt(c(0, 10, 0, 0), c(0, 0, 10, 0)), "POLYGON ((0 0, 10 0, 0 10, 0 0))" ) expect_identical( coords_polygon_translate_wkt( c(20, 10, 10, 30, 45, 30, 20, 20), c(35, 30, 10, 5, 20, 20, 15, 25), ring_id = c(1, 1, 1, 1, 1, 2, 2, 2) ), "POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20))" ) expect_identical( coords_polygon_translate_wkt( c(20, 10, 10, 30, 45, 30, 20, 20, 40, 20, 45), c(35, 30, 10, 5, 20, 20, 15, 25, 40, 45, 30), feature_id = c(rep(1, 8), rep(2, 3)), ring_id = c(1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1) ), c( "POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20))", "POLYGON ((40 40, 20 45, 45 30, 40 40))" ) ) expect_identical( coords_polygon_translate_wkt( c(20, 10, 10, 30, 45, 30, 20, 20, 40, 20, 45), c(35, 30, 10, 5, 20, 20, 15, 25, 40, 45, 30), feature_id = c(rep(1, 8), rep(2, 3)), # new ring should be detected on new feature_id ring_id = c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2) ), c( "POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20))", "POLYGON ((40 40, 20 45, 45 30, 40 40))" ) ) }) test_that("coords_*_translate_wkb() works", { expect_identical( coords_point_translate_wkb(1:3, 2:4), wkt_translate_wkb(c("POINT (1 2)", "POINT (2 3)", "POINT (3 4)")) ) expect_identical( coords_linestring_translate_wkb(1:5, 2:6, feature_id = c(1, 1, 1, 2, 2)), wkt_translate_wkb(c("LINESTRING (1 2, 2 3, 3 4)", "LINESTRING (4 5, 5 6)")) ) expect_identical( coords_polygon_translate_wkb( c(20, 10, 10, 30, 45, 30, 20, 20, 40, 20, 45), c(35, 30, 10, 5, 20, 20, 15, 25, 40, 45, 30), feature_id = c(rep(1, 8), rep(2, 3)), ring_id = c(1, 1, 1, 1, 1, 2, 2, 2, 1, 1, 1) ), wkt_translate_wkb( c( "POLYGON ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20))", "POLYGON ((40 40, 20 45, 45 30, 40 40))" ) ) ) }) wkutils/tests/testthat/test-finite.R0000644000176200001440000000224014163201260017325 0ustar liggesusers test_that("wkt_has_missing() / wkt_is_finite() works", { expect_identical(wkt_has_missing(character(0)), logical(0)) expect_false(wkt_has_missing("POINT EMPTY")) expect_false(wkt_has_missing("POINT (0 1)")) expect_true(wkt_has_missing("POINT (nan nan)")) expect_false(wkt_has_missing("POINT (inf inf)")) expect_true(wkt_is_finite("POINT EMPTY")) expect_true(wkt_is_finite("POINT (0 1)")) expect_false(wkt_is_finite("POINT (nan nan)")) expect_false(wkt_is_finite("POINT (inf inf)")) }) test_that("wkb_has_missing() / wkb_is_finite() works", { expect_identical(wkb_has_missing(list()), logical(0)) # WKB can't do empty point without missing coords expect_false(wkb_has_missing(as_wkb("LINESTRING EMPTY"))) expect_false(wkb_has_missing(as_wkb("POINT (0 1)"))) expect_true(wkb_has_missing(as_wkb("POINT (nan nan)"))) expect_false(wkb_has_missing(as_wkb("POINT (inf inf)"))) expect_identical(wkb_is_finite(list()), logical(0)) expect_true(wkb_is_finite(as_wkb("LINESTRING EMPTY"))) expect_true(wkb_is_finite(as_wkb("POINT (0 1)"))) expect_false(wkb_is_finite(as_wkb("POINT (nan nan)"))) expect_false(wkb_is_finite(as_wkb("POINT (inf inf)"))) }) wkutils/tests/testthat/test-meta.R0000644000176200001440000000717314163201260017007 0ustar liggesusers test_that("wkb_meta() works", { expect_identical( wkb_meta(wkt_translate_wkb("POINT (30 10)")), tibble::tibble( feature_id = 1L, part_id = 1L, type_id = 1L, size = 1L, srid = NA_integer_, has_z = FALSE, has_m = FALSE ) ) }) test_that("wkt_meta() works", { expect_identical( wkt_meta("POINT (30 10)"), tibble::tibble( feature_id = 1L, part_id = 1L, type_id = 1L, size = 1L, srid = NA_integer_, has_z = FALSE, has_m = FALSE ) ) }) test_that("wkt_streamer_meta() works", { # point expect_identical( wkt_streamer_meta("POINT (30 10)"), tibble::tibble( feature_id = 1L, part_id = 1L, type_id = 1L, size = NA_integer_, srid = NA_integer_, has_z = FALSE, has_m = FALSE ) ) # multipoint expect_identical( wkt_streamer_meta("MULTIPOINT ((30 10))", recursive = FALSE), tibble::tibble( feature_id = 1L, part_id = 1L, type_id = 4L, size = NA_integer_, srid = NA_integer_, has_z = FALSE, has_m = FALSE ) ) # multipoint recursive expect_identical( wkt_streamer_meta("MULTIPOINT ((30 10))", recursive = TRUE), tibble::tibble( feature_id = c(1L, 1L), part_id = c(1L, 2L), type_id = c(4L, 1L), size = c(NA_integer_, NA_integer_), srid = c(NA_integer_, NA_integer_), has_z = c(FALSE, FALSE), has_m = c(FALSE, FALSE) ) ) # collection expect_identical( wkt_streamer_meta("GEOMETRYCOLLECTION (POINT (30 10))", recursive = FALSE), tibble::tibble( feature_id = 1L, part_id = 1L, type_id = 7L, size = NA_integer_, srid = NA_integer_, has_z = FALSE, has_m = FALSE ) ) # collection recursive expect_identical( wkt_streamer_meta(c("GEOMETRYCOLLECTION (POINT (30 10))", NA), recursive = TRUE), tibble::tibble( feature_id = c(1L, 1L, 2L), part_id = c(1L, 2L, NA_integer_), type_id = c(7L, 1L, NA_integer_), size = c(NA_integer_, NA_integer_, NA_integer_), srid = c(NA_integer_, NA_integer_, NA_integer_), has_z = c(FALSE, FALSE, NA), has_m = c(FALSE, FALSE, NA) ) ) }) test_that("wkt_streamer_meta() works with NULLs", { expect_identical( wkt_streamer_meta(NA), tibble::tibble( feature_id = 1L, part_id = NA_integer_, type_id = NA_integer_, size = NA_integer_, srid = NA_integer_, has_z = NA, has_m = NA ) ) }) test_that("wkt_meta() counts coordinates when NULLs are present", { expect_identical( wkt_meta(c("LINESTRING (20 20, 0 0)", NA)), tibble::tibble( feature_id = c(1L, 2L), part_id = c(1L, NA_integer_), type_id = c(2L, NA_integer_), size = c(2L, NA_integer_), srid = c(NA_integer_, NA_integer_), has_z = c(FALSE, NA), has_m = c(FALSE, NA) ) ) }) test_that("wkt_streamer_meta() returns SRIDs if present", { expect_identical( wkt_streamer_meta("SRID=33;POINT (30 10)"), tibble::tibble( feature_id = 1L, part_id = 1L, type_id = 1L, size = NA_integer_, srid = 33L, has_z = FALSE, has_m = FALSE ) ) }) test_that("wkt_streamer_meta() fails on parse error", { expect_error(wkt_streamer_meta("NOPE"), class = "WKParseException") }) test_that("geometry type converters work", { types_str <- c( "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection" ) expect_identical(wk_geometry_type_id(types_str), 1:7) expect_identical(wk_geometry_type(7:1), rev(types_str)) }) wkutils/tests/testthat/test-debug.R0000644000176200001440000000215614163201260017143 0ustar liggesusers test_that("debugger works on wkb", { point <- as.raw(c(0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x40)) expect_output(wkb_debug(list(point)), "POINT \\[1\\]") point_bad_type <- point point_bad_type[2] <- as.raw(0xff) expect_output(wkb_debug(list(point_bad_type)), "Invalid integer geometry type") }) test_that("debugger works on wkt stream", { expect_output(wkt_streamer_debug("LINESTRING (30 10, 0 0, 0 1)"), "LINESTRING") expect_output(wkt_streamer_debug("POLYGON ((30 10, 0 0, 0 1, 30 10))"), "POLYGON") expect_output(wkt_streamer_debug("MULTIPOINT (30 10, 0 0)"), "MULTIPOINT") expect_output(wkt_streamer_debug("MULTIPOINT ((30 10), (0 0))"), "MULTIPOINT") }) test_that("debugger works on wkt", { expect_output(wkt_debug("LINESTRING (30 10, 0 0, 0 1)"), "LINESTRING") expect_output(wkt_debug("POLYGON ((30 10, 0 0, 0 1, 30 10))"), "POLYGON") expect_output(wkt_debug("MULTIPOINT (30 10, 0 0)"), "MULTIPOINT") expect_output(wkt_debug("MULTIPOINT ((30 10), (0 0))"), "MULTIPOINT") }) wkutils/tests/testthat/test-draw.R0000644000176200001440000000121114163201260017001 0ustar liggesusers test_that("wkt_draw_* works", { x <- "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" expect_identical(wkt_plot_new(x, main = "wkt_draw_*()"), x) expect_identical(wkt_draw_polypath(x, col = "grey90"), x) expect_identical(wkt_draw_lines(x, col = "red"), x) expect_identical(wkt_draw_points(x, pch = 16), x) }) test_that("wkb_draw_* works", { x <- wkt_translate_wkb("POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))") expect_identical(wkb_plot_new(x, main = "wkb_draw_*()"), x) expect_identical(wkb_draw_polypath(x, col = "grey90"), x) expect_identical(wkb_draw_lines(x, col = "red"), x) expect_identical(wkb_draw_points(x, pch = 16), x) }) wkutils/tests/testthat/test-unnest.R0000644000176200001440000000643514163201260017375 0ustar liggesusers test_that("wk*_unnest() works", { expect_identical(wkt_unnest(NA_character_), structure(NA_character_, lengths = 1L)) expect_identical( wkt_unnest( "GEOMETRYCOLLECTION(MULTIPOINT (30 10, 10 10), LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION EMPTY)", keep_multi = FALSE, keep_empty = FALSE, max_depth = 2 ), structure(c("POINT (30 10)", "POINT (10 10)", "LINESTRING (0 0, 1 1)"), lengths = 3L) ) expect_identical( wkt_unnest( "GEOMETRYCOLLECTION(MULTIPOINT (30 10, 10 10), LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION EMPTY)", keep_multi = FALSE, keep_empty = TRUE, max_depth = 2 ), structure( c("POINT (30 10)", "POINT (10 10)", "LINESTRING (0 0, 1 1)", "GEOMETRYCOLLECTION EMPTY"), lengths = 4L ) ) expect_identical( wkt_unnest( "SRID=12;GEOMETRYCOLLECTION(MULTIPOINT (30 10, 10 10), LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION EMPTY)", keep_multi = FALSE, max_depth = 2 ), structure( c("SRID=12;POINT (30 10)", "SRID=12;POINT (10 10)", "SRID=12;LINESTRING (0 0, 1 1)"), lengths = 3L ) ) expect_identical( wkb_unnest( as_wkb("GEOMETRYCOLLECTION(MULTIPOINT (30 10, 10 10), LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION EMPTY)"), keep_multi = TRUE, max_depth = 2, keep_empty = FALSE ), structure( wkt_translate_wkb(c("MULTIPOINT ((30 10), (10 10))", "LINESTRING (0 0, 1 1)")), lengths = 2L ) ) expect_identical( wkb_unnest( as_wkb("GEOMETRYCOLLECTION(MULTIPOINT (30 10, 10 10), LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION EMPTY)"), keep_multi = TRUE, max_depth = 2, keep_empty = TRUE ), structure( wkt_translate_wkb(c("MULTIPOINT ((30 10), (10 10))", "LINESTRING (0 0, 1 1)", "GEOMETRYCOLLECTION EMPTY")), lengths = 3L ) ) expect_identical( wkb_unnest( as_wkb("GEOMETRYCOLLECTION(MULTIPOINT (30 10, 10 10), LINESTRING (0 0, 1 1), GEOMETRYCOLLECTION EMPTY)"), keep_multi = FALSE, max_depth = 2, keep_empty = FALSE ), structure( wkt_translate_wkb(c("POINT (30 10)", "POINT (10 10)", "LINESTRING (0 0, 1 1)")), lengths = 3L ) ) }) test_that("wk*_unnest(max_depth) is respected", { expect_identical( wkt_unnest( "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1))))", max_depth = 0 ), structure( "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1))))", lengths = 1L ) ) expect_identical( wkt_unnest( "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1))))", max_depth = 1 ), structure("GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1)))", lengths = 1L) ) expect_identical( wkt_unnest( "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1))))", max_depth = 2 ), structure("GEOMETRYCOLLECTION (POINT (0 1))", lengths = 1L) ) expect_identical( wkt_unnest( "GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1))))", max_depth = 3 ), structure("POINT (0 1)", lengths = 1L) ) expect_identical( wkt_unnest( "SRID=21;GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (GEOMETRYCOLLECTION (POINT (0 1))))", max_depth = 3 ), structure("SRID=21;POINT (0 1)", lengths = 1L) ) }) wkutils/tests/testthat/test-filters.R0000644000176200001440000000551314163201260017525 0ustar liggesusers test_that("wkt_set_srid works", { expect_identical(wkt_set_srid(character(0), 1234), character(0)) expect_identical(wkt_set_srid(NA_character_, 1234), NA_character_) expect_identical( wkt_set_srid("POINT (30 10)", 1234), "SRID=1234;POINT (30 10)" ) expect_identical( wkt_set_srid("POINT (30 10)", c(1234, 5678)), c("SRID=1234;POINT (30 10)", "SRID=5678;POINT (30 10)") ) expect_identical( wkt_set_srid(c("POINT (30 10)", "POINT (10 10)"), c(1234, 5678)), c("SRID=1234;POINT (30 10)", "SRID=5678;POINT (10 10)") ) expect_identical( wkt_set_srid(c("POINT (30 10)", "POINT (10 10)"), c(1234)), c("SRID=1234;POINT (30 10)", "SRID=1234;POINT (10 10)") ) }) test_that("wkb_set_srid works", { expect_identical(wkb_set_srid(list(), 1234), list()) expect_identical(wkb_set_srid(list(NULL), 1234), list(NULL)) expect_identical( wkb_set_srid(as_wkb("POINT (30 10)"), 1234), unclass(as_wkb("SRID=1234;POINT (30 10)")) ) }) test_that("wkt_set_z works", { expect_identical(wkt_set_z(character(0), 1234), character(0)) expect_identical(wkt_set_z(NA_character_, 1234), NA_character_) expect_identical( wkt_set_z("POINT (30 10)", 1234), "POINT Z (30 10 1234)" ) expect_identical( wkt_set_z("POINT (30 10)", c(1234, 5678)), c("POINT Z (30 10 1234)", "POINT Z (30 10 5678)") ) expect_identical( wkt_set_z(c("POINT (30 10)", "POINT (10 10)"), c(1234, 5678)), c("POINT Z (30 10 1234)", "POINT Z (10 10 5678)") ) expect_identical( wkt_set_z(c("POINT (30 10)", "POINT (10 10)"), c(1234)), c("POINT Z (30 10 1234)", "POINT Z (10 10 1234)") ) }) test_that("wkb_set_z works", { expect_identical(wkb_set_z(list(), 1234), list()) expect_identical(wkb_set_z(list(NULL), 1234), list(NULL)) expect_identical( wkb_set_z(as_wkb("POINT (30 10)"), 1234), unclass(as_wkb("POINT Z (30 10 1234)")) ) }) test_that("wkt_transform works", { tx <- 3 ty <- 7 t_test_trans <- matrix(c(1, 0, 0, 0, 1, 0, tx, ty, 1), ncol = 3) t_test_rotate45 <- matrix( c(0.707106781186548, -0.707106781186547, 0, 0.707106781186547, 0.707106781186548, 0, 0, 0, 1), ncol = 3 ) expect_identical(wkt_transform("POINT (0 0)", t_test_trans), "POINT (3 7)") expect_identical(wkt_transform("POINT (0 0)", t_test_rotate45), "POINT (0 0)") expect_identical( wkt_transform(sprintf("POINT (%s 0)", sqrt(2)), t_test_rotate45, precision = 10), "POINT (1 -1)" ) expect_identical( wkt_transform( sprintf("POINT (%s 0)", sqrt(2)), t_test_trans %*% t_test_rotate45, precision = 10 ), "POINT (4 6)" ) }) test_that("wkb_transform works", { tx <- 3 ty <- 7 t_test_trans <- matrix(c(1, 0, 0, 0, 1, 0, tx, ty, 1), ncol = 3) expect_identical( wkb_transform(as_wkb("POINT (0 0)"), t_test_trans), wkt_translate_wkb("POINT (3 7)") ) }) wkutils/tests/testthat/test-coords.R0000644000176200001440000000571014163201260017345 0ustar liggesusers test_that("wkb_coords() works", { # point wkb <- wkt_translate_wkb("POINT (30 10)") expect_identical( wkb_coords(wkb), tibble::tibble( feature_id = 1L, part_id = 1L, ring_id = 0L, x = 30, y = 10, z = NA_real_, m = NA_real_ ) ) }) test_that("wkt_coords() works", { # point expect_identical( wkt_coords("POINT (30 10)"), tibble::tibble( feature_id = 1L, part_id = 1L, ring_id = 0L, x = 30, y = 10, z = NA_real_, m = NA_real_ ) ) # point zm expect_identical( wkt_coords("POINT ZM (30 10 1 2)"), tibble::tibble( feature_id = 1L, part_id = 1L, ring_id = 0L, x = 30, y = 10, z = 1, m = 2 ) ) # linestring expect_identical( wkt_coords("LINESTRING (30 10, 20 11)"), tibble::tibble( feature_id = c(1L, 1L), part_id = c(1L, 1L), ring_id = c(0L, 0L), x = c(30, 20), y = c(10, 11), z = c(NA_real_, NA_real_), m = c(NA_real_, NA_real_) ) ) # polygon expect_identical( wkt_coords("POLYGON ((30 10, 20 11, 0 0, 30 10))"), tibble::tibble( feature_id = c(1L, 1L, 1L, 1L), part_id = c(1L, 1L, 1L, 1L), ring_id = c(1L, 1L, 1L, 1L), x = c(30, 20, 0, 30), y = c(10, 11, 0, 10), z = c(NA_real_, NA_real_, NA_real_, NA_real_), m = c(NA_real_, NA_real_, NA_real_, NA_real_) ) ) # multipoint expect_identical( wkt_coords("MULTIPOINT ((30 10), (20 11))"), tibble::tibble( feature_id = c(1L, 1L), part_id = c(2L, 3L), ring_id = c(0L, 0L), x = c(30, 20), y = c(10, 11), z = c(NA_real_, NA_real_), m = c(NA_real_, NA_real_) ) ) # collection # point expect_identical( wkt_coords("GEOMETRYCOLLECTION (POINT (30 10))"), tibble::tibble( feature_id = 1L, part_id = 2L, ring_id = 0L, x = 30, y = 10, z = NA_real_, m = NA_real_ ) ) }) test_that("sep_na works as intended", { holes <- c( "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0), (2 2, 2 4, 4 4, 4 2, 2 2))", "POLYGON ((11 11, 20 11, 20 20, 11 20, 11 11), (12 12, 12 14, 14 14, 14 12, 12 12))" ) expect_identical( wkt_coords(holes, sep_na = FALSE)$ring_id, c(rep(1L, 5), rep(2L, 5), rep(3L, 5), rep(4L, 5)) ) expect_identical( wkt_coords(holes, sep_na = TRUE)$ring_id, c(rep(1L, 5), NA, rep(2L, 5), NA, rep(3L, 5), NA, rep(4L, 5)) ) # multi-geometries should only separate between simple geoms expect_identical( wkt_coords("MULTIPOINT ((30 10), (0 0))", sep_na = TRUE)$part_id, c(2L, NA, 3L) ) # null geoms at the start shouldn't insert a separator expect_identical( wkt_coords(c(NA, "POINT (30 10)"), sep_na = TRUE)$feature_id, 2L ) # empty geoms at the start shouldn't insert a separator expect_identical( wkt_coords(c("POINT EMPTY", "POINT (30 10)"), sep_na = TRUE)$feature_id, 2L ) }) wkutils/tests/testthat/test-plot.R0000644000176200001440000000213714163201260017032 0ustar liggesusers test_that("wk*_plot works", { example_wkt <- c( NA, "POINT (30 10)", "POINT EMPTY", "POINT Z (1 1 5)", "MULTIPOINT (10 40, 40 30, 20 20, 30 10)", "MULTIPOINT EMPTY", "POLYGON ((30 10, 40 40, 20 40, 10 20, 30 10))", "POLYGON ((35 10, 45 45, 15 40, 10 20, 35 10), (20 30, 35 35, 30 20, 20 30))", "POLYGON EMPTY", "MULTIPOINT ((10 40), (40 30), (20 20), (30 10))", "MULTILINESTRING ((10 10, 20 20, 10 40), (40 40, 30 30, 40 20, 30 10))", "MULTILINESTRING EMPTY", "MULTIPOLYGON (((30 20, 45 40, 10 40, 30 20)), ((15 5, 40 10, 10 20, 5 10, 15 5)))", "MULTIPOLYGON ( ((40 40, 20 45, 45 30, 40 40)), ((20 35, 10 30, 10 10, 30 5, 45 20, 20 35), (30 20, 20 15, 20 25, 30 20)) )", "MULTIPOLYGON EMPTY", "GEOMETRYCOLLECTION ( POINT (40 10), LINESTRING (10 10, 20 20, 10 40), POLYGON ((40 40, 20 45, 45 30, 40 40)) )", "GEOMETRYCOLLECTION EMPTY" ) wkt_plot(as_wkt(example_wkt)) wkb_plot(as_wkb(example_wkt)) wkt_plot(unclass(as_wkt(example_wkt))) wkb_plot(unclass(as_wkb(example_wkt))) expect_true(TRUE) }) wkutils/tests/testthat/test-ranges.R0000644000176200001440000000520714163201260017334 0ustar liggesusers test_that("wkt_ranges() works", { expect_identical( wkt_ranges(c("POINT (1 2)", "POINT (5 6)")), tibble::tibble( xmin = 1, ymin = 2, zmin = Inf, mmin = Inf, xmax = 5, ymax = 6, zmax = -Inf, mmax = -Inf ) ) expect_identical( wkt_ranges(c("POINT Z (1 2 3)", "POINT (5 6)")), tibble::tibble( xmin = 1, ymin = 2, zmin = 3, mmin = Inf, xmax = 5, ymax = 6, zmax = 3, mmax = -Inf ) ) expect_identical( wkt_ranges(c("POINT ZM (1 2 3 4)", "POINT (5 6)")), tibble::tibble( xmin = 1, ymin = 2, zmin = 3, mmin = 4, xmax = 5, ymax = 6, zmax = 3, mmax = 4 ) ) expect_identical( wkt_ranges(c("POINT (1 nan)", "POINT (5 6)")), tibble::tibble( xmin = 1, ymin = NA_real_, zmin = Inf, mmin = Inf, xmax = 5, ymax = NA_real_, zmax = -Inf, mmax = -Inf ) ) expect_identical( wkt_ranges(c("POINT (1 nan)", "POINT (5 6)"), na.rm = TRUE), tibble::tibble( xmin = 1, ymin = 6, zmin = Inf, mmin = Inf, xmax = 5, ymax = 6, zmax = -Inf, mmax = -Inf ) ) expect_identical( wkt_ranges(c("POINT (1 inf)", "POINT (5 6)")), tibble::tibble( xmin = 1, ymin = 6, zmin = Inf, mmin = Inf, xmax = 5, ymax = Inf, zmax = -Inf, mmax = -Inf ) ) expect_identical( wkt_ranges(c("POINT (1 inf)", "POINT (5 6)"), finite = TRUE), tibble::tibble( xmin = 1, ymin = 6, zmin = Inf, mmin = Inf, xmax = 5, ymax = 6, zmax = -Inf, mmax = -Inf ) ) expect_identical( wkt_ranges(c("POINT (nan nan)", "POINT (nan nan)"), finite = TRUE), wkt_ranges(character(0)) ) expect_identical( wkt_ranges(c("POINT (1 2)", "POINT (nan nan)"), finite = TRUE), wkt_ranges("POINT (1 2)") ) }) test_that("wkb_ranges() works", { expect_identical( wkb_ranges(wkt_translate_wkb(c("POINT (1 2)", "POINT (5 6)"))), tibble::tibble( xmin = 1, ymin = 2, zmin = Inf, mmin = Inf, xmax = 5, ymax = 6, zmax = -Inf, mmax = -Inf ) ) }) test_that("wkt_feature_ranges() works", { expect_identical( wkt_feature_ranges(c("POINT ZM (1 2 3 4)", "POINT ZM (5 6 7 8)")), tibble::tibble( xmin = c(1, 5), ymin = c(2, 6), zmin = c(3, 7), mmin = c(4, 8), xmax = c(1, 5), ymax = c(2, 6), zmax = c(3, 7), mmax = c(4, 8) ) ) }) test_that("wkb_feature_ranges() works", { expect_identical( wkb_feature_ranges(wkt_translate_wkb(c("POINT ZM (1 2 3 4)", "POINT ZM (5 6 7 8)"))), tibble::tibble( xmin = c(1, 5), ymin = c(2, 6), zmin = c(3, 7), mmin = c(4, 8), xmax = c(1, 5), ymax = c(2, 6), zmax = c(3, 7), mmax = c(4, 8) ) ) }) wkutils/tests/testthat.R0000644000176200001440000000007214163201260015073 0ustar liggesuserslibrary(testthat) library(wkutils) test_check("wkutils") wkutils/src/0000755000176200001440000000000014361406202012541 5ustar liggesuserswkutils/src/wk/0000755000176200001440000000000014357667115013203 5ustar liggesuserswkutils/src/wk/wkt-streamer.hpp0000644000176200001440000004321714164565751016347 0ustar liggesusers #ifndef WK_WKT_STREAMER_H #define WK_WKT_STREAMER_H #include #include #include "wk/reader.hpp" #include "wk/geometry-handler.hpp" #include "wk/io-string.hpp" #include using namespace Rcpp; class WKParseableStringException: public WKParseException { public: WKParseableStringException(std::string expected, std::string found, const char* src, size_t pos): WKParseException(makeError(expected, found, src, pos)), expected(expected), found(found), src(src), pos(pos) {} std::string expected; std::string found; std::string src; size_t pos; static std::string makeError(std::string expected, std::string found, const char* src, size_t pos) { std::stringstream stream; stream << "Expected " << expected << " but found " << found << " (:" << pos << ")"; return stream.str().c_str(); } }; class WKParseableString { public: WKParseableString(const char* str, const char* whitespace, const char* sep): str(str), length(strlen(str)), offset(0), whitespace(whitespace), sep(sep) {} // Change the position of the cursor size_t seek(size_t position) { if (position > this->length) { position = this->length; } else if (position < 0) { position = 0; } size_t delta = position - this->offset; this->offset = position; return delta; } void advance() { if (this->offset < this->length) { this->offset++; } } void advance(int n) { if ((this->offset + n) <= this->length) { this->offset += n; } else { this->offset = this->length; } } bool finished() { return this->offset >= this->length; } // Returns the character at the cursor and advances the cursor // by one char readChar() { char out = this->peekChar(); this->advance(); return out; } // Returns the character currently ahead of the cursor // without advancing the cursor (skips whitespace) char peekChar() { this->skipWhitespace(); if (this->offset < this->length) { return this->str[this->offset]; } else { return '\0'; } } // Returns true if the next character is one of `chars` bool is(char c) { return c == this->peekChar(); } // Returns true if the next character is one of `chars` bool isOneOf(const char* chars) { return strchr(chars, this->peekChar()) != nullptr; } // Returns true if the next character is most likely to be a number bool isNumber() { // complicated by nan and inf if (this->isOneOf("-nNiI")) { std::string text = this->peekUntilSep(); try { std::stod(text); return true; } catch(std::exception& e) { return false; } } else { return this->isOneOf("-0123456789"); } } // Returns true if the next character is a letter bool isLetter() { char found = this->peekChar(); return (found >= 'a' && found <= 'z') || (found >= 'A' && found <= 'Z'); } std::string assertWord() { std::string text = this->peekUntilSep(); if (!this->isLetter()) { this->error("a word", quote(text)); } this->advance(text.size()); return text; } // Returns the integer currently ahead of the cursor, // throwing an exception if whatever is ahead of the // cursor cannot be parsed into an integer uint32_t assertInteger() { std::string text = this->peekUntilSep(); try { uint32_t out = std::stoul(text); this->advance(text.size()); return out; } catch (std::exception& e) { if (this->finished()) { this->error("an integer", "end of input"); } else { this->error("an integer", quote(text)); } } } // Returns the double currently ahead of the cursor, // throwing an exception if whatever is ahead of the // cursor cannot be parsed into a double. This will // accept "inf", "-inf", and "nan". double assertNumber() { std::string text = this->peekUntilSep(); try { double out = std::stod(text); this->advance(text.size()); return out; } catch (std::exception& e) { if (this->finished()) { this->error("a number", "end of input"); } else { this->error("a number", quote(text)); } } } // Asserts that the character at the cursor is whitespace, and // returns a std::string of whitespace characters, advancing the // cursor to the end of the whitespace. std::string assertWhitespace() { if (this->finished()) { this->error("whitespace", "end of input"); } char found = this->str[this->offset]; if (strchr(this->whitespace, found) == nullptr) { this->error("whitespace", quote(this->peekUntilSep())); } size_t offset0 = this->offset; size_t nWhitespaceChars = this->skipWhitespace(); return std::string(&(this->str[offset0]), nWhitespaceChars); } void assert_(char c) { char found = this->peekChar(); if (found != c) { this->error(quote(c), quote(found)); } this->advance(); } // Asserts the that the character at the cursor is one of `chars` // and advances the cursor by one (throwing an exception otherwise). char assertOneOf(const char* chars) { char found = this->peekChar(); if ((strlen(chars) > 0) && this->finished()) { this->error(expectedFromChars(chars), "end of input"); } else if (strchr(chars, found) == nullptr) { this->error(expectedFromChars(chars), quote(this->peekUntilSep())); } this->advance(); return found; } // Asserts that the cursor is at the end of the input void assertFinished() { this->assertOneOf(""); } // Returns the text between the cursor and the next separator, // which is defined to be whitespace or the following characters: =;,() // advancing the cursor. If we are at the end of the string, this will // return std::string("") std::string readUntilSep() { this->skipWhitespace(); size_t wordLen = peekUntil(this->sep); bool finished = this->finished(); if (wordLen == 0 && !finished) { wordLen = 1; } std::string out(&(this->str[this->offset]), wordLen); this->advance(wordLen); return out; } // Returns the text between the cursor and the next separator // (" \r\n\t,();=") without advancing the cursor. std::string peekUntilSep() { this->skipWhitespace(); size_t wordLen = peekUntil(this->sep); if (wordLen == 0 && !this->finished()) { wordLen = 1; } return std::string(&(this->str[this->offset]), wordLen); } // Advances the cursor past any whitespace, returning the // number of characters skipped. size_t skipWhitespace() { return this->skipChars(this->whitespace); } // Skips all of the characters in `chars`, returning the number of // characters skipped. size_t skipChars(const char* chars) { size_t offset0 = this->offset; char c = this->str[this->offset]; while ((c != '\0') && strchr(chars, c)) { this->offset++; if (this->offset >= this->length) { break; } c = this->str[this->offset]; } return this->offset - offset0; } // Returns the number of characters until one of `chars` is encountered, // which may be 0. size_t peekUntil(const char* chars) { size_t offset0 = this->offset; size_t offseti = this->offset; char c = this->str[offseti]; while ((c != '\0') && !strchr(chars, c)) { offseti++; if (offseti >= this->length) { break; } c = this->str[offseti]; } return offseti - offset0; } [[ noreturn ]] void errorBefore(std::string expected, std::string found) { throw WKParseableStringException(expected, quote(found), this->str, this->offset - found.size()); } [[noreturn]] void error(std::string expected, std::string found) { throw WKParseableStringException(expected, found, this->str, this->offset); } [[noreturn]] void error(std::string expected) { throw WKParseableStringException(expected, quote(this->peekUntilSep()), this->str, this->offset); } private: const char* str; size_t length; size_t offset; const char* whitespace; const char* sep; static std::string expectedFromChars(const char* chars) { size_t nChars = strlen(chars); if (nChars == 0) { return "end of input"; } else if (nChars == 1) { return quote(chars); } std::stringstream stream; for (size_t i = 0; i < nChars; i++) { if (nChars > 2) { stream << ","; } if (i > 0) { stream << " or "; } stream << quote(chars[i]); } return stream.str(); } static std::string quote(std::string input) { if (input.size() == 0) { return "end of input"; } else { std::stringstream stream; stream << "'" << input << "'"; return stream.str(); } } static std::string quote(char input) { if (input == '\0') { return "end of input"; } else { std::stringstream stream; stream << "'" << input << "'"; return stream.str(); } } }; class WKTString: public WKParseableString { public: WKTString(const char* str): WKParseableString(str, " \r\n\t", " \r\n\t,();=") {} WKGeometryMeta assertGeometryMeta() { WKGeometryMeta meta; std::string geometryType = this->assertWord(); if (geometryType == "SRID") { this->assert_('='); meta.srid = this->assertInteger(); meta.hasSRID = true; this->assert_(';'); geometryType = this->assertWord(); } if (this->is('Z')) { this->assert_('Z'); meta.hasZ = true; } if (this->is('M')) { this->assert_('M'); meta.hasM = true; } if (this->isEMPTY()) { meta.hasSize = true; meta.size = 0; } meta.geometryType = this->geometryTypeFromString(geometryType); return meta; } int geometryTypeFromString(std::string geometryType) { if (geometryType == "POINT") { return WKGeometryType::Point; } else if(geometryType == "LINESTRING") { return WKGeometryType::LineString; } else if(geometryType == "POLYGON") { return WKGeometryType::Polygon; } else if(geometryType == "MULTIPOINT") { return WKGeometryType::MultiPoint; } else if(geometryType == "MULTILINESTRING") { return WKGeometryType::MultiLineString; } else if(geometryType == "MULTIPOLYGON") { return WKGeometryType::MultiPolygon; } else if(geometryType == "GEOMETRYCOLLECTION") { return WKGeometryType::GeometryCollection; } else { this->errorBefore("geometry type or 'SRID='", geometryType); } } bool isEMPTY() { return this->peekUntilSep() == "EMPTY"; } bool assertEMPTYOrOpen() { if (this->isLetter()) { std::string word = this->assertWord(); if (word != "EMPTY") { this->errorBefore("'(' or 'EMPTY'", word); } return true; } else if (this->is('(')) { this->assert_('('); return false; } else { this->error("'(' or 'EMPTY'"); } } }; class WKTStreamer: public WKReader { public: WKTStreamer(WKStringProvider& provider): WKReader(provider), provider(provider) { // constructor and deleter set the thread locale while the object is in use #ifdef _MSC_VER _configthreadlocale(_ENABLE_PER_THREAD_LOCALE); #endif char* p = std::setlocale(LC_NUMERIC, nullptr); if(p != nullptr) { this->saved_locale = p; } std::setlocale(LC_NUMERIC, "C"); } ~WKTStreamer() { std::setlocale(LC_NUMERIC, saved_locale.c_str()); } void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); if (this->provider.featureIsNull()) { this->handler->nextNull(featureId); } else { std::string str = this->provider.featureString(); WKTString s(str.c_str()); this->readGeometryWithType(s, PART_ID_NONE); // we probably want to assert finished here, but // keeping this commented-out until all examples of this // are removed from downstream packages (notably, s2) // s.assertFinished(); } this->handler->nextFeatureEnd(featureId); } protected: WKStringProvider& provider; void readGeometryWithType(WKTString& s, uint32_t partId) { WKGeometryMeta meta = s.assertGeometryMeta(); this->handler->nextGeometryStart(meta, partId); switch (meta.geometryType) { case WKGeometryType::Point: this->readPoint(s, meta); break; case WKGeometryType::LineString: this->readLineString(s, meta); break; case WKGeometryType::Polygon: this->readPolygon(s, meta); break; case WKGeometryType::MultiPoint: this->readMultiPoint(s, meta); break; case WKGeometryType::MultiLineString: this->readMultiLineString(s, meta); break; case WKGeometryType::MultiPolygon: this->readMultiPolygon(s, meta); break; case WKGeometryType::GeometryCollection: this->readGeometryCollection(s, meta); break; default: throw WKParseException("Unknown geometry type integer"); // # nocov } this->handler->nextGeometryEnd(meta, partId); } void readPoint(WKTString& s, const WKGeometryMeta& meta) { if (!s.assertEMPTYOrOpen()) { this->readPointCoordinate(s, meta); s.assert_(')'); } } void readLineString(WKTString& s, const WKGeometryMeta& meta) { this->readCoordinates(s, meta); } void readPolygon(WKTString& s, const WKGeometryMeta& meta) { this->readLinearRings(s, meta); } uint32_t readMultiPoint(WKTString& s, const WKGeometryMeta& meta) { if (s.assertEMPTYOrOpen()) { return 0; } WKGeometryMeta childMeta; uint32_t partId = 0; if (s.isNumber()) { // (0 0, 1 1) do { childMeta = this->childMeta(s, meta, WKGeometryType::Point); this->handler->nextGeometryStart(childMeta, partId); if (s.isEMPTY()) { s.assertWord(); } else { this->readPointCoordinate(s, childMeta); } this->handler->nextGeometryEnd(childMeta, partId); partId++; } while (s.assertOneOf(",)") != ')'); } else { // ((0 0), (1 1)) do { childMeta = this->childMeta(s, meta, WKGeometryType::Point); this->handler->nextGeometryStart(childMeta, partId); this->readPoint(s, childMeta); this->handler->nextGeometryEnd(childMeta, partId); partId++; } while (s.assertOneOf(",)") != ')'); } return partId; } uint32_t readMultiLineString(WKTString& s, const WKGeometryMeta& meta) { if (s.assertEMPTYOrOpen()) { return 0; } WKGeometryMeta childMeta; uint32_t partId = 0; do { childMeta = this->childMeta(s, meta, WKGeometryType::LineString); this->handler->nextGeometryStart(childMeta, partId); this->readLineString(s, childMeta); this->handler->nextGeometryEnd(childMeta, partId); partId++; } while (s.assertOneOf(",)") != ')'); return partId; } uint32_t readMultiPolygon(WKTString& s, const WKGeometryMeta& meta) { if (s.assertEMPTYOrOpen()) { return 0; } WKGeometryMeta childMeta; uint32_t partId = 0; do { childMeta = this->childMeta(s, meta, WKGeometryType::Polygon); this->handler->nextGeometryStart(childMeta, partId); this->readPolygon(s, childMeta); this->handler->nextGeometryEnd(childMeta, partId); partId++; } while (s.assertOneOf(",)") != ')'); return partId; } uint32_t readGeometryCollection(WKTString& s, const WKGeometryMeta& meta) { if (s.assertEMPTYOrOpen()) { return 0; } uint32_t partId = 0; do { this->readGeometryWithType(s, partId); partId++; } while (s.assertOneOf(",)") != ')'); return partId; } uint32_t readLinearRings(WKTString& s, const WKGeometryMeta& meta) { if (s.assertEMPTYOrOpen()) { return 0; } uint32_t ringId = 0; do { this->handler->nextLinearRingStart(meta, WKGeometryMeta::SIZE_UNKNOWN, ringId); this->readCoordinates(s, meta); this->handler->nextLinearRingEnd(meta, WKGeometryMeta::SIZE_UNKNOWN, ringId); ringId++; } while (s.assertOneOf(",)") != ')'); return ringId; } // Point coordinates are special in that there can only be one // coordinate (and reading more than one might cause errors since // writers are unlikely to expect a point geometry with many coordinates). // This assumes that `s` has already been checked for EMPTY or an opener // since this is different for POINT (...) and MULTIPOINT (.., ...) uint32_t readPointCoordinate(WKTString& s, const WKGeometryMeta& meta) { WKCoord coord = this->childCoordinate(meta); this->readCoordinate(s, coord); handler->nextCoordinate(meta, coord, 0); return 1; } uint32_t readCoordinates(WKTString& s, const WKGeometryMeta& meta) { WKCoord coord = this->childCoordinate(meta); if (s.assertEMPTYOrOpen()) { return 0; } uint32_t coordId = 0; do { this->readCoordinate(s, coord); handler->nextCoordinate(meta, coord, coordId); coordId++; } while (s.assertOneOf(",)") != ')'); return coordId; } void readCoordinate(WKTString& s, WKCoord& coord) { coord[0] = s.assertNumber(); for (size_t i = 1; i < coord.size(); i++) { s.assertWhitespace(); coord[i] = s.assertNumber(); } } WKCoord childCoordinate(const WKGeometryMeta& meta) { WKCoord coord; coord.hasZ = meta.hasZ; coord.hasM = meta.hasM; return coord; } WKGeometryMeta childMeta(WKTString& s, const WKGeometryMeta& parent, int geometryType) { WKGeometryMeta childMeta(parent); childMeta.geometryType = geometryType; if (s.isEMPTY()) { childMeta.hasSize = true; childMeta.size = 0; } else { childMeta.hasSize = false; childMeta.size = WKGeometryMeta::SIZE_UNKNOWN; } return childMeta; } private: std::string saved_locale; }; #endif wkutils/src/wk/geometry-formatter.hpp0000644000176200001440000000256614164565751017560 0ustar liggesusers #ifndef WK_GEOMETRY_FORMATTER_H #define WK_GEOMETRY_FORMATTER_H #include "wk/geometry-handler.hpp" #include "wk/wkb-reader.hpp" #include "wk/wkt-streamer.hpp" #include "wk/wkt-writer.hpp" class WKMaxCoordinatesException: public WKParseException { public: static const int CODE_HAS_MAX_COORDS = 32453; WKMaxCoordinatesException(): WKParseException(CODE_HAS_MAX_COORDS) {} }; class WKGeometryFormatter: public WKTWriter { public: WKGeometryFormatter(WKStringExporter& exporter, int maxCoords): WKTWriter(exporter), maxCoords(maxCoords), thisFeatureCoords(0) {} void nextFeatureStart(size_t featureId) { this->thisFeatureCoords = 0; WKTWriter::nextFeatureStart(featureId); } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { WKTWriter::nextCoordinate(meta, coord, coordId); this->thisFeatureCoords++; if (this->thisFeatureCoords >= this->maxCoords) { throw WKMaxCoordinatesException(); } } bool nextError(WKParseException& error, size_t featureId) { if (error.code() == WKMaxCoordinatesException::CODE_HAS_MAX_COORDS) { this->exporter.writeConstChar("..."); } else { this->exporter.writeConstChar("!!! "); this->exporter.writeConstChar(error.what()); } this->nextFeatureEnd(featureId); return true; } private: int maxCoords; int thisFeatureCoords; }; #endif wkutils/src/wk/wkb-reader.hpp0000644000176200001440000001202414164565751015735 0ustar liggesusers #ifndef WK_WKB_READER_H #define WK_WKB_READER_H #include "wk/reader.hpp" #include "wk/parse-exception.hpp" #include "wk/geometry-meta.hpp" #include "wk/io-bytes.hpp" #include "wk/geometry-handler.hpp" #include "wk/coord.hpp" class WKBReader: public WKReader { public: const static unsigned char ENDIAN_NONE = 0xff; WKBReader(WKBytesProvider& provider): WKReader(provider), provider(provider) { this->swapEndian = false; this->featureId = 0; this->partId = PART_ID_NONE; this->ringId = RING_ID_NONE; this->coordId = COORD_ID_NONE; this->srid = WKGeometryMeta::SRID_NONE; this->endian = ENDIAN_NONE; } void iterateFeature() { this->endian = ENDIAN_NONE; WKReader::iterateFeature(); } protected: WKBytesProvider& provider; unsigned char endian; void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); if (this->provider.featureIsNull()) { this->handler->nextNull(featureId); } else { this->readGeometry(PART_ID_NONE); } this->handler->nextFeatureEnd(featureId); } void readGeometry(uint32_t partId) { WKGeometryMeta meta = this->readMeta(); this->handler->nextGeometryStart(meta, partId); switch (meta.geometryType) { case WKGeometryType::Point: this->readPoint(meta); break; case WKGeometryType::LineString: this->readLineString(meta); break; case WKGeometryType::Polygon: this->readPolygon(meta); break; case WKGeometryType::MultiPoint: case WKGeometryType::MultiLineString: case WKGeometryType::MultiPolygon: case WKGeometryType::GeometryCollection: this->readCollection(meta); break; default: // # nocov start std::stringstream err; err << "Invalid integer geometry type: " << meta.geometryType; throw WKParseException(err.str()); // # nocov end } this->handler->nextGeometryEnd(meta, partId); } WKGeometryMeta readMeta() { this->endian = this->readChar(); this->swapEndian = ((int)endian != (int)WKBytesUtils::nativeEndian()); WKGeometryMeta meta = WKGeometryMeta(this->readUint32()); if (meta.hasSRID) { meta.srid = this->readUint32(); this->srid = meta.srid; } if (meta.geometryType == WKGeometryType::Point) { meta.hasSize = true; meta.size = 1; } else { meta.hasSize = true; meta.size = this->readUint32(); } return meta; } void readPoint(const WKGeometryMeta& meta) { this->readCoordinate(meta, 0); } void readLineString(const WKGeometryMeta& meta) { for (uint32_t i=0; i < meta.size; i++) { this->coordId = i; this->readCoordinate(meta, i); } } void readPolygon(WKGeometryMeta& meta) { uint32_t ringSize; for (uint32_t i=0; i < meta.size; i++) { this->ringId = i; ringSize = this->readUint32(); this->readLinearRing(meta, ringSize, i); } } void readLinearRing(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->handler->nextLinearRingStart(meta, size, ringId); for (uint32_t i=0; i < size; i++) { this->coordId = i; this->readCoordinate(meta, i); } this->handler->nextLinearRingEnd(meta, size, ringId); } void readCollection(const WKGeometryMeta& meta) { for (uint32_t i=0; i < meta.size; i++) { this->partId = i; this->readGeometry(i); } } void readCoordinate(const WKGeometryMeta& meta, uint32_t coordId) { this->x = this->readDouble(); this->y = this->readDouble(); if (meta.hasZ && meta.hasM) { this->z = this->readDouble(); this->m = this->readDouble(); this->handler->nextCoordinate(meta, WKCoord::xyzm(x, y, z, m), coordId); } else if (meta.hasZ) { this->z = this->readDouble(); this->handler->nextCoordinate(meta, WKCoord::xyz(x, y, z), coordId); } else if (meta.hasM) { this->m = this->readDouble(); this->handler->nextCoordinate(meta, WKCoord::xym(x, y, m), coordId); } else { this->handler->nextCoordinate(meta, WKCoord::xy(x, y), coordId); } } // endian swapping is hard to replicate...these might be useful // for subclasses that implement an extension of WKB unsigned char readChar() { return this->readCharRaw(); } double readDouble() { if (this->swapEndian) { return WKBytesUtils::swapEndian(this->readDoubleRaw()); } else return this->readDoubleRaw(); } private: bool swapEndian; uint32_t partId; uint32_t ringId; uint32_t coordId; uint32_t srid; double x; double y; double z; double m; double readUint32() { if (this->swapEndian) { return WKBytesUtils::swapEndian(this->readUint32Raw()); } else return this->readUint32Raw(); } unsigned char readCharRaw() { return this->provider.readCharRaw(); } double readDoubleRaw() { return this->provider.readDoubleRaw(); } uint32_t readUint32Raw() { return this->provider.readUint32Raw(); } bool seekNextFeature() { return this->provider.seekNextFeature(); } }; #endif wkutils/src/wk/xyzm.hpp0000644000176200001440000000642314164565751014727 0ustar liggesusers #ifndef WK_XYZM_HPP #define WK_XYZM_HPP #include #include "wk/fields.hpp" template class WKXYZMReader: public WKFieldsReader { public: WKXYZMReader(WKFieldsProvider& provider): WKFieldsReader(provider) {} void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); double x = this->provider.template field(0); double y = this->provider.template field(1); double z = this->provider.template field(2); double m = this->provider.template field(3); WKGeometryMeta meta(WKGeometryType::Point); meta.hasSize = true; meta.hasZ = !std::isnan(z); meta.hasM = !std::isnan(m); // treat NA, NA, NA as an empty point if (std::isnan(x) && std::isnan(y) && std::isnan(z) && std::isnan(m)) { meta.size = 0; this->handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); this->handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } else { meta.size = 1; WKCoord coord = WKCoord::xyzm(x, y, z, m); coord.hasZ = meta.hasZ; coord.hasM = meta.hasM; this->handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); this->handler->nextCoordinate(meta, coord, 0); this->handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } this->handler->nextFeatureEnd(featureId); } }; template class WKXYZMWriter: public WKFieldsWriter { public: WKXYZMWriter(WKFieldsExporter& exporter): WKFieldsWriter(exporter) {} virtual void nextFeatureStart(size_t featureId) { WKFieldsWriter::nextFeatureStart(featureId); } void nextNull(size_t featureId) { this->exporter.template setField(0, NAN); this->exporter.template setField(1, NAN); this->exporter.template setField(2, NAN); this->exporter.template setField(3, NAN); } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { if (meta.geometryType != WKGeometryType::Point) { throw std::runtime_error("Can't create xy(zm) from a non-point"); } if (meta.size == 0) { this->exporter.template setField(0, NAN); this->exporter.template setField(1, NAN); this->exporter.template setField(2, NAN); this->exporter.template setField(3, NAN); } } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->exporter.template setField(0, coord.x); this->exporter.template setField(1, coord.y); if (coord.hasZ) { this->exporter.template setField(2, coord.z); } else { this->exporter.template setField(2, NAN); } if (coord.hasM) { this->exporter.template setField(3, coord.m); } else { this->exporter.template setField(3, NAN); } } }; #endif wkutils/src/wk/geometry-handler.hpp0000644000176200001440000000165014164565751017163 0ustar liggesusers #ifndef WK_GEOMETRY_HANDLER_H #define WK_GEOMETRY_HANDLER_H #include "wk/coord.hpp" #include "wk/parse-exception.hpp" #include "wk/geometry-meta.hpp" class WKGeometryHandler { public: virtual void nextFeatureStart(size_t featureId) { } virtual void nextFeatureEnd(size_t featureId) { } virtual void nextNull(size_t featureId) { } virtual void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { } virtual void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { } virtual void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { } virtual void nextLinearRingEnd(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { } virtual void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { } virtual bool nextError(WKParseException& error, size_t featureId) { return false; } }; #endif wkutils/src/wk/geometry-debug-handler.hpp0000644000176200001440000000773114164565751020255 0ustar liggesusers #ifndef WK_GEOMETRY_DEBUG_HANDLER_H #define WK_GEOMETRY_DEBUG_HANDLER_H #include "wk/coord.hpp" #include "wk/geometry-handler.hpp" #include "wk/parse-exception.hpp" #include "wk/geometry-meta.hpp" #include "wk/geometry-debug-handler.hpp" class WKGeometryDebugHandler: public WKGeometryHandler { public: WKGeometryDebugHandler(std::ostream& out): out(out), indentationLevel(0) {} virtual void nextFeatureStart(size_t featureId) { this->indentationLevel = 0; this->indent(); out << "nextFeatureStart(" << featureId << ")\n"; this->indentationLevel++; } virtual void nextFeatureEnd(size_t featureId) { this->indentationLevel--; this->indent(); out << "nextFeatureEnd(" << featureId << ")\n"; } virtual void nextNull(size_t featureId) { this->indent(); out << "nextNull(" << featureId << ")\n"; } virtual void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->indent(); out << "nextGeometryStart("; this->writeMeta(meta); out << ", "; this->writeMaybeUnknown(partId, "WKReader::PART_ID_NONE"); out << ")\n"; this->indentationLevel++; } virtual void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { this->indentationLevel--; this->indent(); out << "nextGeometryEnd("; this->writeMeta(meta); out << ", "; this->writeMaybeUnknown(partId, "WKReader::PART_ID_NONE"); out << ")\n"; } virtual void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->indent(); out << "nextLinearRingStart("; this->writeMeta(meta); out << ", "; this->writeMaybeUnknown(size, "WKGeometryMeta::SIZE_UNKNOWN"); out << ", " << ringId << ")\n"; this->indentationLevel++; } virtual void nextLinearRingEnd(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->indentationLevel--; this->indent(); out << "nextLinearRingEnd("; this->writeMeta(meta); out << ", "; this->writeMaybeUnknown(size, "WKGeometryMeta::SIZE_UNKNOWN"); out << ", " << ringId << ")\n"; } virtual void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->indent(); out << "nextCoordinate("; this->writeMeta(meta); out << ", " << "WKCoord(x = " << coord.x << ", y = " << coord.y; if (coord.hasZ) { out << ", z = " << coord.z; } if (coord.hasM) { out << ", m = " << coord.m; } out << "), " << coordId << ")\n"; } virtual bool nextError(WKParseException& error, size_t featureId) { out << "nextError('" << error.what() << "', " << featureId << ")\n"; return true; } virtual void writeMaybeUnknown(uint32_t value, const char* ifUnknown) { if (value == UINT32_MAX) { out << ifUnknown; } else { out << value; } } virtual void writeMeta(const WKGeometryMeta& meta) { this->writeGeometryType(meta.geometryType); if (meta.hasSRID) { out << " SRID=" << meta.srid; } if (meta.hasSize) { out << " [" << meta.size << "]"; } else { out << " [unknown]"; } } virtual void writeGeometryType(uint32_t simpleGeometryType) { switch (simpleGeometryType) { case WKGeometryType::Point: out << "POINT"; break; case WKGeometryType::LineString: out << "LINESTRING"; break; case WKGeometryType::Polygon: out << "POLYGON"; break; case WKGeometryType::MultiPoint: out << "MULTIPOINT"; break; case WKGeometryType::MultiLineString: out << "MULTILINESTRING"; break; case WKGeometryType::MultiPolygon: out << "MULTIPOLYGON"; break; case WKGeometryType::GeometryCollection: out << "GEOMETRYCOLLECTION"; break; default: out << "Unknown Type (" << simpleGeometryType << ")"; break; } } virtual void indent() { for (int i=0; i < indentationLevel; i++) { out << " "; } } protected: std::ostream& out; int indentationLevel; }; #endif wkutils/src/wk/io-string.hpp0000644000176200001440000000274014357667115015632 0ustar liggesusers #ifndef WK_IO_STRING_H #define WK_IO_STRING_H #include #include #include #include "wk/io.hpp" // for now, the only option is to provide a reference to a string // the string tokenizer operates on a string iterator, which might be // more flexible for the WKT reader but less flexible for other applications class WKStringProvider: public WKProvider { public: virtual const std::string featureString() = 0; }; class WKStringExporter: public WKExporter { public: WKStringExporter(size_t size): WKExporter(size) {} virtual void writeString(std::string value) = 0; virtual void writeConstChar(const char* value) = 0; virtual void writeDouble(double value) = 0; virtual void writeUint32(uint32_t value) = 0; }; class WKStringStreamExporter: public WKStringExporter { public: WKStringStreamExporter(size_t size): WKStringExporter(size) { this->stream.imbue(std::locale::classic()); } void setRoundingPrecision(int precision) { this->stream.precision(precision); } void setTrim(bool trim) { if (trim) { this->stream.unsetf(stream.fixed); } else { this->stream.setf(stream.fixed); } } void writeString(std::string value) { this->stream << value; } void writeConstChar(const char* value) { this->stream << value; } void writeDouble(double value) { this->stream << value; } void writeUint32(uint32_t value) { this->stream << value; } protected: std::stringstream stream; }; #endif wkutils/src/wk/writer.hpp0000644000176200001440000000376514164565751015242 0ustar liggesusers #ifndef WK_WRITER_H #define WK_WRITER_H #include "wk/geometry-handler.hpp" #include "wk/geometry-meta.hpp" #include "wk/io.hpp" class WKWriter: public WKGeometryHandler { public: // by default, leave everything as is! WKWriter(WKExporter& exporter): exporter(exporter), includeZ(2), includeM(2), includeSRID(2) {} virtual void nextFeatureStart(size_t featureId) { exporter.prepareNextFeature(); } virtual void nextNull(size_t featureId) { exporter.writeNull(); } virtual void nextFeatureEnd(size_t featureId) { exporter.writeNextFeature(); } // creation options for all WKX formats void setIncludeSRID(int includeSRID) { this->includeSRID = includeSRID; } void setIncludeZ(int includeZ) { this->includeZ = includeZ; } void setIncludeM(int includeM) { this->includeM = includeM; } protected: WKExporter& exporter; int includeZ; int includeM; int includeSRID; WKGeometryMeta newMeta; virtual WKGeometryMeta getNewMeta(const WKGeometryMeta& meta) { WKGeometryMeta newMeta( meta.geometryType, this->actuallyIncludeZ(meta), this->actuallyIncludeM(meta), this->actuallyIncludeSRID(meta) ); newMeta.srid = meta.srid; newMeta.hasSize = meta.hasSize; newMeta.size = meta.size; return newMeta; } bool actuallyIncludeZ(const WKGeometryMeta& meta) { return actuallyInclude(this->includeZ, meta.hasZ, "Z"); } bool actuallyIncludeM(const WKGeometryMeta& meta) { return actuallyInclude(this->includeM, meta.hasM, "M"); } bool actuallyIncludeSRID(const WKGeometryMeta& meta) { return actuallyInclude(this->includeSRID, meta.hasSRID, "SRID"); } bool actuallyInclude(int flag, bool hasValue, const char* label) { if (flag == 1 && !hasValue) { std::stringstream err; err << "Can't include " << label << " values in a geometry for which " << label << " values are not defined"; throw std::runtime_error(err.str()); } return flag && hasValue; } }; #endif wkutils/src/wk/rcpp-coord-reader.hpp0000644000176200001440000002133314164565751017225 0ustar liggesusers #ifndef WK_RCPP_COORD_READER_H #define WK_RCPP_COORD_READER_H #include #include "wk/io.hpp" #include "wk/coord.hpp" #include "wk/reader.hpp" #include "wk/geometry-meta.hpp" #include class WKRcppPointCoordProvider: public WKProvider { public: WKRcppPointCoordProvider(Rcpp::NumericVector x, Rcpp::NumericVector y, Rcpp::NumericVector z, Rcpp::NumericVector m): x(x), y(y), z(z), m(m), index(-1) {} void readFeature(WKGeometryHandler* handler) { if (((size_t) this->index) >= this->nFeatures() || this->index < 0) { throw std::runtime_error("attempt to access index out of range"); } if (this->coordEmpty(this->index)) { WKGeometryMeta meta(WKGeometryType::Point, 0); handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } else { WKCoord coord = this->coord(this->index); WKGeometryMeta meta(WKGeometryType::Point, 1); meta.hasZ = coord.hasZ; meta.hasM = coord.hasM; handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); handler->nextCoordinate(meta, coord, 0); handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } } WKCoord coord(R_xlen_t i) { double xi = x[i]; double yi = y[i]; double zi = z[i]; double mi = m[i]; if (std::isnan(zi) && std::isnan(mi)) { return WKCoord::xy(xi, yi); } else if (std::isnan(mi)) { return WKCoord::xyz(xi, yi, zi); } else if (std::isnan(zi)) { return WKCoord::xym(xi, yi, mi); } else { return WKCoord::xyzm(xi, yi, zi, mi); } } bool coordEmpty(R_xlen_t i) { return std::isnan(x[i]) && std::isnan(y[i]) && std::isnan(z[i]) && std::isnan(m[i]); } virtual bool seekNextFeature() { this->index++; if (this->index >= this->nFeatures()) { return false; } else { return true; } } virtual size_t nFeatures() { return this->x.size(); } bool featureIsNull() { return false; } void reset() { this->index = -1; } protected: Rcpp::NumericVector x; Rcpp::NumericVector y; Rcpp::NumericVector z; Rcpp::NumericVector m; R_xlen_t index; }; class WKRcppLinestringCoordProvider: public WKRcppPointCoordProvider { public: WKRcppLinestringCoordProvider(Rcpp::NumericVector x, Rcpp::NumericVector y, Rcpp::NumericVector z, Rcpp::NumericVector m, Rcpp::IntegerVector featureId): WKRcppPointCoordProvider(x, y, z, m), featureId(featureId), nSizes(-1) {} virtual void readFeature(WKGeometryHandler* handler) { if (this->index >= this->nFeatures() || this->index < 0) { throw std::runtime_error("attempt to access index out of range"); } uint32_t size = this->sizes[this->index]; R_xlen_t offset = this->offsets[this->index]; WKCoord firstCoord = this->coord(offset); WKGeometryMeta meta(WKGeometryType::LineString, size); meta.hasZ = firstCoord.hasZ; meta.hasM = firstCoord.hasM; handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); for (uint32_t i = 0; i < size; i++) { WKCoord coord = this->coord(offset + i); handler->nextCoordinate(meta, coord, i); } handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } virtual size_t nFeatures() { if (this->nSizes == -1) { if (featureId.size() == 0) { this->nSizes = 0; return this->nSizes; } R_xlen_t currentSize = 0; this->offsets.push_back(0); for (R_xlen_t i = 1; i < featureId.length(); i++) { currentSize++; if (this->featureId[i - 1] != this->featureId[i]) { this->sizes.push_back(currentSize); currentSize = 0; this->offsets.push_back(i); } } this->sizes.push_back(currentSize + 1); this->nSizes = this->offsets.size(); } return this->nSizes; } protected: Rcpp::IntegerVector featureId; R_xlen_t nSizes; std::vector sizes; std::vector offsets; }; class WKRcppPolygonCoordProvider: public WKRcppPointCoordProvider { public: WKRcppPolygonCoordProvider(Rcpp::NumericVector x, Rcpp::NumericVector y, Rcpp::NumericVector z, Rcpp::NumericVector m, Rcpp::IntegerVector featureId, Rcpp::IntegerVector ringId): WKRcppPointCoordProvider(x, y, z, m), featureId(featureId), ringId(ringId), nSizes(-1) {} virtual void readFeature(WKGeometryHandler* handler) { if (this->index >= this->nFeatures() || this->index < 0) { throw std::runtime_error("attempt to access index out of range"); } R_xlen_t featureOffset = this->offsets[this->index]; WKCoord firstCoord = this->coord(featureOffset); WKGeometryMeta meta(WKGeometryType::Polygon, this->ringSizes[this->index].size()); meta.hasZ = firstCoord.hasZ; meta.hasM = firstCoord.hasM; handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); R_xlen_t offset = featureOffset; for (uint32_t i = 0; i < meta.size; i++) { uint32_t ringSize = this->ringSizes[this->index][i]; bool ringIsClosed = this->ringClosed[this->index][i]; uint32_t ringSizeOut = ringSize + !ringIsClosed; firstCoord = this->coord(offset); handler->nextLinearRingStart(meta, ringSizeOut, i); for (uint32_t j = 0; j < ringSize; j++) { WKCoord coord = this->coord(offset + j); handler->nextCoordinate(meta, coord, j); } if (!ringIsClosed) { handler->nextCoordinate(meta, firstCoord, ringSize); } handler->nextLinearRingEnd(meta, ringSize, i); offset += ringSize; } handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } virtual size_t nFeatures() { if (this->nSizes == -1) { if (featureId.size() == 0) { this->nSizes = 0; return this->nSizes; } R_xlen_t currentSize = 0; WKCoord firstCoord = this->coord(0); std::vector featureRingClosed; std::vector featureRingSizes; this->offsets.push_back(0); for (R_xlen_t i = 1; i < featureId.length(); i++) { currentSize++; bool isRingTransition = currentSize > 1 && this->ringId[i - 1] != this->ringId[i]; bool isFeatureTransition = this->featureId[i - 1] != this->featureId[i]; if (isRingTransition || isFeatureTransition) { WKCoord lastCoord = this->coord(i - 1); featureRingClosed.push_back(lastCoord == firstCoord); featureRingSizes.push_back(currentSize); currentSize = 0; firstCoord = this->coord(i); } if (isFeatureTransition) { this->ringClosed.push_back(std::move(featureRingClosed)); this->ringSizes.push_back(std::move(featureRingSizes)); featureRingClosed = std::vector(); featureRingSizes = std::vector(); this->offsets.push_back(i); } } WKCoord lastCoord = this->coord(featureId.length() - 1); featureRingClosed.push_back(lastCoord == firstCoord); featureRingSizes.push_back(currentSize + 1); this->ringClosed.push_back(std::move(featureRingClosed)); this->ringSizes.push_back(std::move(featureRingSizes)); this->nSizes = this->offsets.size(); } return this->nSizes; } protected: Rcpp::IntegerVector featureId; Rcpp::IntegerVector ringId; R_xlen_t nSizes; std::vector> ringSizes; std::vector> ringClosed; std::vector offsets; }; class WKRcppPointCoordReader: public WKReader { public: WKRcppPointCoordReader(WKRcppPointCoordProvider& provider): WKReader(provider), provider(provider) {} void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); this->provider.readFeature(this->handler); this->handler->nextFeatureEnd(featureId); } protected: WKRcppPointCoordProvider& provider; }; class WKRcppLinestringCoordReader: public WKReader { public: WKRcppLinestringCoordReader(WKRcppLinestringCoordProvider& provider): WKReader(provider), provider(provider) {} void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); this->provider.readFeature(this->handler); this->handler->nextFeatureEnd(featureId); } protected: WKRcppLinestringCoordProvider& provider; }; class WKRcppPolygonCoordReader: public WKReader { public: WKRcppPolygonCoordReader(WKRcppPolygonCoordProvider& provider): WKReader(provider), provider(provider) {} void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); this->provider.readFeature(this->handler); this->handler->nextFeatureEnd(featureId); } protected: WKRcppPolygonCoordProvider& provider; }; #endif wkutils/src/wk/geometry.hpp0000644000176200001440000000303114164565751015543 0ustar liggesusers #ifndef WK_GEOMETRY_H #define WK_GEOMETRY_H #include #include #include "wk/geometry-meta.hpp" #include "wk/coord.hpp" class WKGeometry { public: WKGeometry(WKGeometryMeta meta): meta(meta) {} virtual ~WKGeometry() {} WKGeometryMeta meta; virtual uint32_t size() = 0; virtual void addCoordinate(const WKCoord& coord) = 0; }; class WKPoint: public WKGeometry { public: WKPoint(WKGeometryMeta meta): WKGeometry(meta) {} std::vector coords; uint32_t size() { return coords.size(); } void addCoordinate(const WKCoord& coord) { coords.push_back(coord); } }; class WKLineString: public WKGeometry { public: WKLineString(WKGeometryMeta meta): WKGeometry(meta) {} std::vector coords; uint32_t size() { return coords.size(); } void addCoordinate(const WKCoord& coord) { coords.push_back(coord); } }; class WKLinearRing: public std::vector {}; class WKPolygon: public WKGeometry { public: WKPolygon(WKGeometryMeta meta): WKGeometry(meta) {} std::vector rings; uint32_t size() { return rings.size(); } void addCoordinate(const WKCoord& coord) { rings[rings.size() - 1].push_back(coord); } }; class WKCollection: public WKGeometry { public: WKCollection(WKGeometryMeta meta): WKGeometry(meta) {} std::vector> geometries; uint32_t size() { return geometries.size(); } void addCoordinate(const WKCoord& coord) { geometries[geometries.size() - 1]->addCoordinate(coord); } }; #endif wkutils/src/wk/parse-exception.hpp0000644000176200001440000000073514164565751017026 0ustar liggesusers #ifndef WK_PARSE_EXCEPTION_H #define WK_PARSE_EXCEPTION_H #include #include class WKParseException: public std::runtime_error { public: static const int CODE_UNSPECIFIED = 0; WKParseException(int code): std::runtime_error(""), exceptionCode(code) {} WKParseException(std::string message): std::runtime_error(message), exceptionCode(CODE_UNSPECIFIED) {} int code() { return this->exceptionCode; } private: int exceptionCode; }; #endif wkutils/src/wk/reader.hpp0000644000176200001440000000244114164565751015156 0ustar liggesusers #ifndef WK_READER_H #define WK_READER_H #include "wk/geometry-meta.hpp" #include "wk/geometry-handler.hpp" #include "wk/io.hpp" class WKReader { public: const static uint32_t PART_ID_NONE = UINT32_MAX; const static uint32_t RING_ID_NONE = UINT32_MAX; const static uint32_t COORD_ID_NONE = UINT32_MAX; WKReader(WKProvider& provider): handler(nullptr), provider(provider) { this->reset(); } virtual void reset() { this->provider.reset(); this->featureId = 0; } virtual void setHandler(WKGeometryHandler* handler) { this->handler = handler; } virtual bool hasNextFeature() { return this->provider.seekNextFeature(); } virtual void iterateFeature() { // check to make sure there is a valid handler if (handler == nullptr) { throw std::runtime_error("Unset handler in WKReader::iterateFeature()"); } try { this->readFeature(this->featureId); } catch (WKParseException& error) { if (!handler->nextError(error, this->featureId)) { throw error; } } this->featureId++; } virtual size_t nFeatures() { return this->provider.nFeatures(); } protected: WKGeometryHandler* handler; size_t featureId; virtual void readFeature(size_t featureId) = 0; private: WKProvider& provider; }; #endif wkutils/src/wk/fields.hpp0000644000176200001440000000457314164565751015172 0ustar liggesusers #ifndef WK_FIELDS_HPP #define WK_FIELDS_HPP #include #include "wk/io.hpp" #include "wk/reader.hpp" #include "wk/writer.hpp" template class WKFieldsProvider: public WKProvider { public: WKFieldsProvider(const ContainerType& container, uint32_t size): container(container), size(size), index(UINT32_MAX) {} template ItemType field(size_t field) { const VectorType& vector = this->container[field]; return vector[this->index]; } virtual size_t nFields() { return this->container.size(); } size_t nFeatures() { return this->size; } // whether or not a feature is null has to be resolved // by individual type readers...without knowing anything // about the vector types it isn't possible bool featureIsNull() { return false; } void reset() { this->index = UINT32_MAX; } bool seekNextFeature() { if (this->index == UINT32_MAX) { this->index = 0; } else { this->index++; } return this->index < this->nFeatures(); } protected: const ContainerType& container; private: uint32_t size; uint32_t index; }; template class WKFieldsExporter: public WKExporter { public: WKFieldsExporter(ContainerType container, size_t size): WKExporter(size), container(container), index(0) {} template void setField(size_t field, ItemType value) { VectorType vector = this->container[field]; vector[this->index] = value; } size_t nFields() { return this->container.size(); } void prepareNextFeature() {} void writeNull() { throw std::runtime_error("writeNull() not meaningful for WKFieldsExporter"); } void writeNextFeature() { this->index++; } protected: ContainerType container; size_t index; }; template class WKFieldsReader: public WKReader { public: WKFieldsReader(WKFieldsProvider& provider): WKReader(provider), provider(provider) {} protected: WKFieldsProvider& provider; }; template class WKFieldsWriter: public WKWriter { public: WKFieldsWriter(WKFieldsExporter& exporter): WKWriter(exporter), exporter(exporter) {} virtual void nextNull(size_t featureId) = 0; protected: WKFieldsExporter& exporter; }; #endif wkutils/src/wk/io.hpp0000644000176200001440000000101214164565751014314 0ustar liggesusers #ifndef WK_IO_H #define WK_IO_H class WKProvider { public: virtual bool seekNextFeature() = 0; virtual bool featureIsNull() = 0; virtual size_t nFeatures() = 0; virtual void reset() = 0; virtual ~WKProvider() {} }; class WKExporter { public: WKExporter(size_t size): size(size) {} virtual void prepareNextFeature() = 0; virtual void writeNull() = 0; virtual void writeNextFeature() = 0; size_t nFeatures() { return this->size; } virtual ~WKExporter() {} private: size_t size; }; #endif wkutils/src/wk/coord.hpp0000644000176200001440000000351414164565751015024 0ustar liggesusers #ifndef WK_WKCOORD_H #define WK_WKCOORD_H #include #include #include class WKCoord { public: double x; double y; double z; double m; bool hasZ; bool hasM; WKCoord(): x(NAN), y(NAN), z(NAN), m(NAN), hasZ(false), hasM(false) {} WKCoord(double x, double y, double z, double m, bool hasZ, bool hasM): x(x), y(y), z(z), m(m), hasZ(hasZ), hasM(hasM) {} bool operator == (WKCoord& other) { if (this->hasZ != other.hasZ || this->hasM != other.hasM) { return false; } for (size_t i = 0; i < this->size(); i++) { if ((*this)[i] != other[i]) { return false; } } return true; } double& operator[](std::size_t idx) { switch (idx) { case 0: return x; case 1: return y; case 2: if (hasZ) { return z; } else if (hasM) { return m; } case 3: if (hasM) return m; default: throw std::runtime_error("Coordinate subscript out of range"); } } const double& operator[](std::size_t idx) const { switch (idx) { case 0: return x; case 1: return y; case 2: if (hasZ) { return z; } else if (hasM) { return m; } case 3: if (hasM) return m; default: throw std::runtime_error("Coordinate subscript out of range"); } } const size_t size() const { return 2 + hasZ + hasM; } static const WKCoord xy(double x, double y) { return WKCoord(x, y, NAN, NAN, false, false); } static const WKCoord xyz(double x, double y, double z) { return WKCoord(x, y, z, NAN, true, false); } static const WKCoord xym(double x, double y, double m) { return WKCoord(x, y, NAN, m, false, true); } static const WKCoord xyzm(double x, double y, double z, double m) { return WKCoord(x, y, z, m, true, true); } }; #endif wkutils/src/wk/rct.hpp0000644000176200001440000000377514164565751014517 0ustar liggesusers #ifndef WK_RCT_HPP #define WK_RCT_HPP #include #include "wk/fields.hpp" template class WKRctReader: public WKFieldsReader { public: WKRctReader(WKFieldsProvider& provider): WKFieldsReader(provider) {} void readFeature(size_t featureId) { this->handler->nextFeatureStart(featureId); double xmin = this->provider.template field(0); double ymin = this->provider.template field(1); double xmax = this->provider.template field(2); double ymax = this->provider.template field(3); WKGeometryMeta meta(WKGeometryType::Polygon, false, false, false); meta.hasSize = true; // treat any rectangle with a nan or -Inf width or height as empty // width/height of Inf *is* allowed, since this could be used to encode // a rectangle covering everything double width = xmax - xmin; double height = ymax - ymin; if ((std::isnan(width)) || (std::isnan(height)) || (width == -INFINITY) || (height == -INFINITY)) { meta.size = 0; this->handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); this->handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } else { meta.size = 1; this->handler->nextGeometryStart(meta, WKReader::PART_ID_NONE); this->handler->nextLinearRingStart(meta, 5, 0); this->handler->nextCoordinate(meta, WKCoord::xy(xmin, ymin), 0); this->handler->nextCoordinate(meta, WKCoord::xy(xmax, ymin), 1); this->handler->nextCoordinate(meta, WKCoord::xy(xmax, ymax), 2); this->handler->nextCoordinate(meta, WKCoord::xy(xmin, ymax), 3); this->handler->nextCoordinate(meta, WKCoord::xy(xmin, ymin), 4); this->handler->nextLinearRingEnd(meta, 5, 0); this->handler->nextGeometryEnd(meta, WKReader::PART_ID_NONE); } this->handler->nextFeatureEnd(featureId); } }; #endif wkutils/src/wk/wkt-reader.hpp0000644000176200001440000001230714164565751015763 0ustar liggesusers #ifndef WK_WKT_READER_H #define WK_WKT_READER_H #include #include "wk/wkt-streamer.hpp" #include "wk/geometry.hpp" #include "wk/reader.hpp" #include "wk/io-string.hpp" #include "wk/error-formatter.hpp" #include "wk/geometry-handler.hpp" #include "wk/parse-exception.hpp" #include "wk/coord.hpp" class WKTReader: public WKReader, private WKGeometryHandler { public: WKTReader(WKStringProvider& provider): WKReader(provider), baseReader(provider), feature(nullptr) { this->baseReader.setHandler(this); } void readFeature(size_t featureId) { baseReader.readFeature(featureId); } protected: void nextFeatureStart(size_t featureId) { this->stack.clear(); this->handler->nextFeatureStart(featureId); } void nextNull(size_t featureId) { this->handler->nextNull(featureId); this->feature = std::unique_ptr(nullptr); } void nextFeatureEnd(size_t featureId) { if (this->feature) { this->readGeometry(*feature, PART_ID_NONE); } this->handler->nextFeatureEnd(featureId); } void readGeometry(const WKGeometry& geometry, uint32_t partId) { this->handler->nextGeometryStart(geometry.meta, partId); switch (geometry.meta.geometryType) { case WKGeometryType::Point: this->readPoint((WKPoint&)geometry); break; case WKGeometryType::LineString: this->readLinestring((WKLineString&)geometry); break; case WKGeometryType::Polygon: this->readPolygon((WKPolygon&)geometry); break; case WKGeometryType::MultiPoint: case WKGeometryType::MultiLineString: case WKGeometryType::MultiPolygon: case WKGeometryType::GeometryCollection: this->readCollection((WKCollection&)geometry); break; default: throw WKParseException( ErrorFormatter() << "Unrecognized geometry type: " << geometry.meta.geometryType ); } this->handler->nextGeometryEnd(geometry.meta, partId); } void readPoint(const WKPoint& geometry) { for (uint32_t i=0; i < geometry.coords.size(); i++) { this->handler->nextCoordinate(geometry.meta, geometry.coords[i], i); } } void readLinestring(const WKLineString& geometry) { for (uint32_t i=0; i < geometry.coords.size(); i++) { this->handler->nextCoordinate(geometry.meta, geometry.coords[i], i); } } void readPolygon(const WKPolygon& geometry) { uint32_t nRings = geometry.rings.size(); for (uint32_t i=0; i < nRings; i++) { uint32_t ringSize = geometry.rings[i].size(); this->handler->nextLinearRingStart(geometry.meta, ringSize, i); for (uint32_t j=0; j < ringSize; j++) { this->handler->nextCoordinate(geometry.meta, geometry.rings[i][j], j); } this->handler->nextLinearRingEnd(geometry.meta, ringSize, i); } } void readCollection(const WKCollection& geometry) { for (uint32_t i=0; i < geometry.meta.size; i++) { this->readGeometry(*geometry.geometries[i], i); } } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { switch (meta.geometryType) { case WKGeometryType::Point: this->stack.push_back(std::unique_ptr(new WKPoint(meta))); break; case WKGeometryType::LineString: this->stack.push_back(std::unique_ptr(new WKLineString(meta))); break; case WKGeometryType::Polygon: this->stack.push_back(std::unique_ptr(new WKPolygon(meta))); break; case WKGeometryType::MultiPoint: case WKGeometryType::MultiLineString: case WKGeometryType::MultiPolygon: case WKGeometryType::GeometryCollection: this->stack.push_back(std::unique_ptr(new WKCollection(meta))); break; default: throw WKParseException( ErrorFormatter() << "Unrecognized geometry type: " << meta.geometryType ); } } void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { // there is almost certainly a better way to do this std::unique_ptr currentPtr(this->stack[this->stack.size() - 1].release()); this->stack.pop_back(); // set the size meta currentPtr->meta.size = currentPtr->size(); currentPtr->meta.hasSize = true; // if the parent is a collection, add this geometry to the collection if (stack.size() >= 1) { if (WKCollection* parent = dynamic_cast(&this->current())){ parent->geometries.push_back(std::unique_ptr(currentPtr.release())); } } else if (stack.size() == 0) { this->feature = std::unique_ptr(currentPtr.release()); } } void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { ((WKPolygon&)this->current()).rings.push_back(WKLinearRing()); } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->current().addCoordinate(coord); } bool nextError(WKParseException& error, size_t featureId) { return this->handler->nextError(error, featureId); } protected: WKTStreamer baseReader; std::vector> stack; std::unique_ptr feature; WKGeometry& current() { return *stack[stack.size() - 1]; } }; #endif wkutils/src/wk/rcpp-translate.hpp0000644000176200001440000000632514164565751016660 0ustar liggesusers #ifndef WK_RCPP_TRANSLATE_HPP #define WK_RCPP_TRANSLATE_HPP #include "wk/wkt-writer.hpp" #include "wk/wkt-reader.hpp" #include "wk/wkb-writer.hpp" #include "wk/wkb-reader.hpp" #include "wk/xyzm.hpp" #include "wk/rct.hpp" #include #include "wk/rcpp-io.hpp" class RcppWKFieldsProvider: public WKFieldsProvider { public: RcppWKFieldsProvider(const List& container): WKFieldsProvider(container, Rf_xlength(container[0])) {} }; class RcppFieldsExporter: public WKFieldsExporter { public: RcppFieldsExporter(const List& container): WKFieldsExporter(container, Rf_xlength(container[0])) {} }; class RcppXYZMReader: public WKXYZMReader { public: RcppXYZMReader(RcppWKFieldsProvider& provider): WKXYZMReader(provider) {} }; class RcppXYZMWriter: public WKXYZMWriter { public: RcppXYZMWriter(RcppFieldsExporter& exporter): WKXYZMWriter(exporter) {} }; class RcppWKRctReader: public WKRctReader { public: RcppWKRctReader(RcppWKFieldsProvider& provider): WKRctReader(provider) {} }; namespace wk { inline void rcpp_translate_base(WKReader& reader, WKWriter& writer, int includeZ = NA_INTEGER, int includeM = NA_INTEGER, int includeSRID = NA_INTEGER) { writer.setIncludeZ(includeZ); writer.setIncludeM(includeM); writer.setIncludeSRID(includeSRID); reader.setHandler(&writer); while (reader.hasNextFeature()) { Rcpp::checkUserInterrupt(); reader.iterateFeature(); } } inline Rcpp::List rcpp_translate_wkb(WKReader& reader, int endian, int bufferSize = 2048, int includeZ = NA_INTEGER, int includeM = NA_INTEGER, int includeSRID = NA_INTEGER) { WKRawVectorListExporter exporter(reader.nFeatures()); exporter.setBufferSize(bufferSize); WKBWriter writer(exporter); writer.setEndian(endian); rcpp_translate_base(reader, writer, includeZ, includeM, includeSRID); return exporter.output; } inline Rcpp::CharacterVector rcpp_translate_wkt(WKReader& reader, int precision = 16, bool trim = true, int includeZ = NA_INTEGER, int includeM = NA_INTEGER, int includeSRID = NA_INTEGER) { WKCharacterVectorExporter exporter(reader.nFeatures()); exporter.setRoundingPrecision(precision); exporter.setTrim(trim); WKTWriter writer(exporter); rcpp_translate_base(reader, writer, includeZ, includeM, includeSRID); return exporter.output; } Rcpp::List rcpp_translate_xyzm(WKReader& reader, int includeZ = NA_INTEGER, int includeM = NA_INTEGER) { Rcpp::List xyzm = List::create( _["x"] = NumericVector(reader.nFeatures()), _["y"] = NumericVector(reader.nFeatures()), _["z"] = NumericVector(reader.nFeatures()), _["m"] = NumericVector(reader.nFeatures()) ); RcppFieldsExporter exporter(xyzm); RcppXYZMWriter writer(exporter); rcpp_translate_base(reader, writer, includeZ, includeM, false); return xyzm; } } // namespace wk #endif wkutils/src/wk/wkt-writer.hpp0000644000176200001440000000743614164565751016044 0ustar liggesusers #ifndef WK_WKT_WRITER_H #define WK_WKT_WRITER_H #include #include "wk/io-string.hpp" #include "wk/geometry-handler.hpp" #include "wk/writer.hpp" #include "wk/wkb-reader.hpp" class WKTWriter: public WKWriter { public: WKTWriter(WKStringExporter& exporter): WKWriter(exporter), exporter(exporter) {} void nextFeatureStart(size_t featureId) { this->stack.clear(); WKWriter::nextFeatureStart(featureId); } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->stack.push_back(meta); this->newMeta = this->getNewMeta(meta); this->writeGeometrySep(this->newMeta, partId, this->newMeta.srid); this->writeGeometryOpen(meta.size); } void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { this->writeGeometryClose(meta.size); this->stack.pop_back(); } void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->writeRingSep(ringId); this->exporter.writeConstChar("("); } void nextLinearRingEnd(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->exporter.writeConstChar(")"); } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->writeCoordSep(coordId); this->exporter.writeDouble(coord.x); this->exporter.writeConstChar(" "); this->exporter.writeDouble(coord.y); if (this->newMeta.hasZ && coord.hasZ) { this->exporter.writeConstChar(" "); this->exporter.writeDouble(coord.z); } if (this->newMeta.hasM && coord.hasM) { this->exporter.writeConstChar(" "); this->exporter.writeDouble(coord.m); } } protected: WKStringExporter& exporter; std::vector stack; void writeGeometryOpen(uint32_t size) { if (size == 0) { this->exporter.writeConstChar("EMPTY"); } else { this->exporter.writeConstChar("("); } } void writeGeometryClose(uint32_t size) { if (size > 0) { this->exporter.writeConstChar(")"); } } void writeGeometrySep(const WKGeometryMeta& meta, uint32_t partId, uint32_t srid) { bool iterCollection = iteratingCollection(); bool iterMulti = iteratingMulti(); if ((iterCollection || iterMulti) && partId > 0) { this->exporter.writeConstChar(", "); } if(iterMulti) { return; } if(!iterCollection && meta.hasSRID) { this->exporter.writeConstChar("SRID="); this->exporter.writeUint32(srid); this->exporter.writeConstChar(";"); } this->exporter.writeString(meta.wktType()); this->exporter.writeConstChar(" "); } void writeRingSep(uint32_t ringId) { if (ringId > 0) { this->exporter.writeConstChar(", "); } } void writeCoordSep(uint32_t coordId) { if (coordId > 0) { this->exporter.writeConstChar(", "); } } // stack accessors const WKGeometryMeta lastGeometryType(int level) { if (level >= 0) { return this->stack[level]; } else { return this->stack[this->stack.size() + level]; } } const WKGeometryMeta lastGeometryType() { return lastGeometryType(-1); } size_t recursionLevel() { return this->stack.size(); } bool iteratingMulti() { size_t stackSize = this->recursionLevel(); if (stackSize <= 1) { return false; } const WKGeometryMeta nester = this->lastGeometryType(-2); return nester.geometryType == WKGeometryType::MultiPoint || nester.geometryType == WKGeometryType::MultiLineString || nester.geometryType == WKGeometryType::MultiPolygon; } bool iteratingCollection() { size_t stackSize = this->recursionLevel(); if (stackSize <= 1) { return false; } const WKGeometryMeta nester = this->lastGeometryType(-2); return nester.geometryType == WKGeometryType::GeometryCollection; } }; #endif wkutils/src/wk/io-bytes.hpp0000644000176200001440000000216114164565751015446 0ustar liggesusers #ifndef WK_IO_BYTES_H #define WK_IO_BYTES_H #include #include "wk/io.hpp" class WKBytesUtils { public: // https://github.com/r-spatial/sf/blob/master/src/wkb.cpp // https://stackoverflow.com/questions/105252/how-do-i-convert-between-big-endian-and-little-endian-values-in-c template static T swapEndian(T u) { union { T u; unsigned char u8[sizeof(T)]; } source, dest; source.u = u; for (size_t k = 0; k < sizeof(T); k++) dest.u8[k] = source.u8[sizeof(T) - k - 1]; return dest.u; } static char nativeEndian(void) { const int one = 1; unsigned char *cp = (unsigned char *) &one; return (char) *cp; } }; class WKBytesProvider: public WKProvider { public: virtual unsigned char readCharRaw() = 0; virtual double readDoubleRaw() = 0; virtual uint32_t readUint32Raw() = 0; }; class WKBytesExporter: public WKExporter { public: WKBytesExporter(size_t size): WKExporter(size) {} virtual size_t writeCharRaw(unsigned char value) = 0; virtual size_t writeDoubleRaw(double value) = 0; virtual size_t writeUint32Raw(uint32_t value) = 0; }; #endif wkutils/src/wk/error-formatter.hpp0000644000176200001440000000146714164565751017055 0ustar liggesusers #ifndef WK_ERROR_FORMATTER #define WK_ERROR_FORMATTER // https://stackoverflow.com/questions/12261915/how-to-throw-stdexceptions-with-variable-messages #include #include #include class ErrorFormatter { public: ErrorFormatter() {} ~ErrorFormatter() {} template ErrorFormatter & operator << (const Type & value) { stream_ << value; return *this; } std::string str() const { return stream_.str(); } operator std::string () const { return stream_.str(); } enum ConvertToString { to_str }; std::string operator >> (ConvertToString) { return stream_.str(); } private: std::stringstream stream_; ErrorFormatter(const ErrorFormatter &); ErrorFormatter & operator = (ErrorFormatter &); }; # endif wkutils/src/wk/filter.hpp0000644000176200001440000000570414164565751015206 0ustar liggesusers #ifndef WK_FILTER_H #define WK_FILTER_H #include "wk/geometry-handler.hpp" class WKFilter: public WKGeometryHandler { public: WKFilter(WKGeometryHandler& handler): handler(handler) {} virtual void nextFeatureStart(size_t featureId) { this->handler.nextFeatureStart(featureId); } virtual void nextFeatureEnd(size_t featureId) { this->handler.nextFeatureEnd(featureId); } virtual void nextNull(size_t featureId) { this->handler.nextNull(featureId); } virtual void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->handler.nextGeometryStart(meta, partId); } virtual void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { this->handler.nextGeometryEnd(meta, partId); } virtual void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->handler.nextLinearRingStart(meta, size, ringId); } virtual void nextLinearRingEnd(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->handler.nextLinearRingEnd(meta, size, ringId); } virtual void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->handler.nextCoordinate(meta, coord, coordId); } virtual bool nextError(WKParseException& error, size_t featureId) { return this->handler.nextError(error, featureId); } protected: WKGeometryHandler& handler; }; class WKMetaFilter: public WKFilter { public: WKMetaFilter(WKGeometryHandler& handler): WKFilter(handler) {} virtual WKGeometryMeta newGeometryMeta(const WKGeometryMeta& meta, uint32_t partId) = 0; virtual void nextFeatureStart(size_t featureId) { this->metaReplacement.clear(); this->handler.nextFeatureStart(featureId); } virtual void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->metaReplacement[meta.id()] = this->newGeometryMeta(meta, partId); this->handler.nextGeometryStart(this->metaReplacement[meta.id()], partId); } virtual void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { this->handler.nextGeometryEnd(this->metaReplacement[meta.id()], partId); } virtual void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->handler.nextLinearRingStart(this->metaReplacement[meta.id()], size, ringId); } virtual void nextLinearRingEnd(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->handler.nextLinearRingEnd(this->metaReplacement[meta.id()], size, ringId); } virtual void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->handler.nextCoordinate(this->metaReplacement[meta.id()], coord, coordId); } protected: // using a hash map to keep track of meta, because it's important to make sure that // identical meta objects are used for identical geometry // objects (used in s2 and elsewhere to handle nested collections) std::unordered_map metaReplacement; }; #endif wkutils/src/wk/geometry-meta.hpp0000644000176200001440000001020114164565751016464 0ustar liggesusers #ifndef WK_GEOMETRY_TYPE_H #define WK_GEOMETRY_TYPE_H #include #include #include #include "parse-exception.hpp" // https://github.com/postgis/postgis/blob/2.1.0/doc/ZMSgeoms.txt // https://github.com/r-spatial/sf/blob/master/src/wkb.cpp enum WKGeometryType { Invalid = 0, Point = 1, LineString = 2, Polygon = 3, MultiPoint = 4, MultiLineString = 5, MultiPolygon = 6, GeometryCollection = 7 }; #define EWKB_Z_BIT 0x80000000 #define EWKB_M_BIT 0x40000000 #define EWKB_SRID_BIT 0x20000000 class WKGeometryMeta { public: const static uint32_t SRID_NONE = 0; const static uint32_t SIZE_UNKNOWN = UINT32_MAX; // type info uint32_t geometryType; bool hasZ; bool hasM; bool hasSRID; bool hasSize; uint32_t size; uint32_t srid; WKGeometryMeta(): geometryType(WKGeometryType::Invalid), hasZ(false), hasM(false), hasSRID(false), hasSize(false), size(SIZE_UNKNOWN), srid(SRID_NONE) {} WKGeometryMeta(uint32_t geometryType, uint32_t size = SIZE_UNKNOWN): geometryType(wkbSimpleGeometryType(geometryType)), hasZ(wkbTypeHasZ(geometryType)), hasM(wkbTypeHasM(geometryType)), hasSRID(geometryType & EWKB_SRID_BIT), hasSize(size != SIZE_UNKNOWN), size(size), srid(SRID_NONE) {} WKGeometryMeta(int geometryType, bool hasZ, bool hasM, bool hasSRID): geometryType(geometryType), hasZ(hasZ), hasM(hasM), hasSRID(hasSRID), hasSize(false), size(SIZE_UNKNOWN), srid(SRID_NONE) {} uint32_t ewkbType() { return calcEWKBType(this->geometryType, this->hasZ, this->hasM, this->hasSRID); } std::string wktType() const { std::stringstream f; f << wktSimpleGeometryType(this->geometryType); if (this->hasZ || this->hasM) { f << " "; } if (this->hasZ) { f << "Z"; } if (this->hasM) { f << "M"; } return f.str(); } // this is easier to store than a const WKGeometryMeta&, and safer than // casting to WKGeometryMeta* for testing identical geometryMeta objects uintptr_t id() const { return (uintptr_t) this; } private: static uint32_t calcEWKBType(int simpleGeometryType, bool hasZ, bool hasM, bool hasSRID) { uint32_t out = simpleGeometryType; if (hasZ) out |= EWKB_Z_BIT; if (hasM) out |= EWKB_M_BIT; if (hasSRID) out |= EWKB_SRID_BIT; return out; } static const char* wktSimpleGeometryType(uint32_t simpleGeometryType) { switch (simpleGeometryType) { case WKGeometryType::Point: return "POINT"; case WKGeometryType::LineString: return "LINESTRING"; case WKGeometryType::Polygon: return "POLYGON"; case WKGeometryType::MultiPoint: return "MULTIPOINT"; case WKGeometryType::MultiLineString: return "MULTILINESTRING"; case WKGeometryType::MultiPolygon: return "MULTIPOLYGON"; case WKGeometryType::GeometryCollection: return "GEOMETRYCOLLECTION"; default: // # nocov start std::stringstream err; err << "Invalid integer geometry type: " << simpleGeometryType; throw WKParseException(err.str()); // # nocov end } } // the 1000 + simpleGeometryType and 3000 + simpleGeometryType // series both have Z values as well as those marked with the // EWKB_Z_BIT static bool wkbTypeHasZ(uint32_t geometryType) { if (geometryType & EWKB_Z_BIT) { return true; } geometryType = geometryType & 0x0000ffff; return (geometryType >= 1000 && geometryType < 2000) || (geometryType > 3000); } static bool wkbTypeHasM(uint32_t geometryType) { if (geometryType & EWKB_M_BIT) { return true; } geometryType = geometryType & 0x0000ffff; return geometryType >= 2000; } // has to deal with both EWKB flags and the 1000-style WKB types static uint32_t wkbSimpleGeometryType(uint32_t geometryType) { geometryType = geometryType & 0x0000ffff; if (geometryType >= 3000) { return geometryType - 3000; } else if (geometryType >= 2000) { return geometryType - 2000; } else if (geometryType >= 1000) { return geometryType - 1000; } else { return geometryType; } } }; #endif wkutils/src/wk/rcpp-io.hpp0000644000176200001440000001503114164565751015264 0ustar liggesusers #ifndef WK_RCPP_IO_H #define WK_RCPP_IO_H #include "wk/parse-exception.hpp" #include "wk/io-bytes.hpp" #include "wk/io-string.hpp" #include class WKRcppSEXPProvider: public WKProvider { public: const Rcpp::List& input; R_xlen_t index; WKRcppSEXPProvider(const Rcpp::List& input): input(input) { this->reset(); } void reset() { this->index = -1; } SEXP feature() { return this->input[this->index]; } bool seekNextFeature() { this->index++; return this->index < input.size(); } bool featureIsNull() { return this->input[this->index] == R_NilValue; } size_t nFeatures() { return input.size(); } }; class WKRcppSEXPExporter: public WKExporter { public: Rcpp::List output; R_xlen_t index; WKRcppSEXPExporter(size_t size): WKExporter(size), output(size), index(0) {} void prepareNextFeature() {} void setFeature(SEXP item) { this->item = item; } void writeNull() { this->setFeature(R_NilValue); } void writeNextFeature() { if (this->index >= output.size()) { Rcpp::stop("Attempt to set index out of range (WKRcppSEXPExporter)"); } this->output[this->index] = this->item; this->index++; } private: SEXP item; }; class WKRawVectorListProvider: public WKBytesProvider { public: WKRawVectorListProvider(const Rcpp::List& container): container(container) { this->reset(); } void reset() { this->index = -1; this->featureNull = true; this->offset = 0; } unsigned char readCharRaw() { return readBinary(); } double readDoubleRaw() { return readBinary(); } uint32_t readUint32Raw() { return readBinary(); } bool seekNextFeature() { this->index += 1; if (this->index >= this->container.size()) { return false; } SEXP item = this->container[this->index]; if (item == R_NilValue) { this->featureNull = true; this->data = nullptr; this->dataSize = 0; } else { this->featureNull = false; this->data = RAW(item); this->dataSize = Rf_xlength(item); } this->offset = 0; return true; } bool featureIsNull() { return this->featureNull; } size_t nFeatures() { return container.size(); } private: const Rcpp::List& container; R_xlen_t index; unsigned char* data; size_t dataSize; size_t offset; bool featureNull; template T readBinary() { if ((this->offset + sizeof(T)) > this->dataSize) { throw WKParseException("Reached end of RawVector input"); } T dst; memcpy(&dst, &(this->data[this->offset]), sizeof(T)); this->offset += sizeof(T); return dst; } }; class WKRawVectorListExporter: public WKBytesExporter { public: Rcpp::List output; std::vector buffer; bool featureNull; R_xlen_t index; size_t offset; WKRawVectorListExporter(size_t size): WKBytesExporter(size), buffer(2048) { this->featureNull = false; this->index = 0; this->offset = 0; output = Rcpp::List(size); } void prepareNextFeature() { this->offset = 0; this->featureNull = false; } void writeNull() { this->featureNull = true; } void writeNextFeature() { if (this->index >= output.size()) { Rcpp::stop("Attempt to set index out of range (WKRawVectorListExporter)"); } if (this->featureNull) { this->output[this->index] = R_NilValue; } else { Rcpp::RawVector item(this->offset); memcpy(&(item[0]), &(this->buffer[0]), this->offset); this->output[this->index] = item; } this->index++; } void setBufferSize(R_xlen_t bufferSize) { if (bufferSize <= 0) { throw std::runtime_error("Attempt to set zero or negative buffer size"); } this->buffer = std::vector(bufferSize); } void extendBufferSize(R_xlen_t bufferSize) { if (bufferSize < ((R_xlen_t) this->buffer.size())) { throw std::runtime_error("Attempt to shrink RawVector buffer size"); } std::vector newBuffer(bufferSize); memcpy(&newBuffer[0], &(this->buffer[0]), this->offset); this->buffer = newBuffer; } size_t writeCharRaw(unsigned char value) { return this->writeBinary(value); } size_t writeDoubleRaw(double value) { return this->writeBinary(value); } size_t writeUint32Raw(uint32_t value) { return this->writeBinary(value); } template size_t writeBinary(T value) { // Rcout << "Writing " << sizeof(T) << "(" << value << ") starting at " << this->offset << "\n"; while ((this->offset + sizeof(T)) > ((size_t) this->buffer.size())) { // we're going to need a bigger boat this->extendBufferSize(this->buffer.size() * 2); } memcpy(&(this->buffer[this->offset]), &value, sizeof(T)); this->offset += sizeof(T); return sizeof(T); } }; class WKCharacterVectorProvider: public WKStringProvider { public: const Rcpp::CharacterVector& container; R_xlen_t index; bool featureNull; std::string data; WKCharacterVectorProvider(const Rcpp::CharacterVector& container): container(container) { this->reset(); } void reset() { this->index = -1; this->featureNull = false; } bool seekNextFeature() { this->index++; if (this->index >= this->container.size()) { return false; } if (Rcpp::CharacterVector::is_na(this->container[this->index])) { this->featureNull = true; this->data = std::string(""); } else { this->featureNull = false; this->data = Rcpp::as(this->container[this->index]); } return true; } const std::string featureString() { return this->data; } bool featureIsNull() { return this->featureNull; } size_t nFeatures() { return container.size(); } }; class WKCharacterVectorExporter: public WKStringStreamExporter { public: Rcpp::CharacterVector output; R_xlen_t index; bool featureNull; WKCharacterVectorExporter(size_t size): WKStringStreamExporter(size), output(size), index(0), featureNull(false) {} void prepareNextFeature() { this->featureNull = false; this->stream.str(""); this->stream.clear(); } void writeNull() { this->featureNull = true; } void writeNextFeature() { if (this->index >= output.size()) { Rcpp::stop("Attempt to set index out of range (WKCharacterVectorExporter)"); } if (this->featureNull) { this->output[this->index] = NA_STRING; } else { this->output[this->index] = this->stream.str(); } this->index++; } }; #endif wkutils/src/wk/wkb-writer.hpp0000644000176200001440000000635214164565751016016 0ustar liggesusers #ifndef WK_WKB_WRITER_H #define WK_WKB_WRITER_H #include "wk/geometry-handler.hpp" #include "wk/io-bytes.hpp" #include "wk/writer.hpp" #include "wk/wkb-reader.hpp" class WKBWriter: public WKWriter { public: WKBWriter(WKBytesExporter& exporter): WKWriter(exporter), exporter(exporter), level(0) {} void nextFeatureStart(size_t featureId) { WKWriter::nextFeatureStart(featureId); this->level = 0; } void setEndian(unsigned char endian) { this->endian = endian; this->swapEndian = WKBytesUtils::nativeEndian() != endian; } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->level++; // make sure meta has a valid size if (!meta.hasSize || meta.size == WKGeometryMeta::SIZE_UNKNOWN) { throw std::runtime_error("Can't write WKB wihout a valid meta.size"); } // make a new geometry type based on the creation options this->newMeta = this->getNewMeta(meta); // never include SRID if not a top-level geometry if (this->level > 1) { this->newMeta.srid = WKGeometryMeta::SRID_NONE; this->newMeta.hasSRID = false; } this->writeEndian(); this->writeUint32(this->newMeta.ewkbType()); if (this->newMeta.hasSRID) this->writeUint32(this->newMeta.srid); if (this->newMeta.geometryType != WKGeometryType::Point) this->writeUint32(meta.size); // empty point hack! could also error here, but this feels more in line with // how these are represented in real life (certainly in R) if (this->newMeta.geometryType == WKGeometryType::Point && this->newMeta.size == 0) { this->writeDouble(NAN); this->writeDouble(NAN); if (this->newMeta.hasZ) { this->writeDouble(NAN); } if (this->newMeta.hasM) { this->writeDouble(NAN); } } } void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->writeUint32(size); } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->writeDouble(coord.x); this->writeDouble(coord.y); if (this->newMeta.hasZ && coord.hasZ) { this->writeDouble(coord.z); } if (this->newMeta.hasM && coord.hasM) { this->writeDouble(coord.m); } } void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { this->level--; } private: bool swapEndian; unsigned char endian; WKBytesExporter& exporter; int level; size_t writeEndian() { return this->writeChar(this->endian); } size_t writeCoord(WKCoord coord) { size_t bytesWritten = 0; for (size_t i=0; i < coord.size(); i++) { bytesWritten += this->writeDouble(coord[i]); } return bytesWritten; } size_t writeChar(unsigned char value) { return this->exporter.writeCharRaw(value); } size_t writeDouble(double value) { if (this->swapEndian) { this->exporter.writeDoubleRaw(WKBytesUtils::swapEndian(value)); } else { this->exporter.writeDoubleRaw(value); } return sizeof(double); } size_t writeUint32(uint32_t value) { if (this->swapEndian) { this->exporter.writeUint32Raw(WKBytesUtils::swapEndian(value)); } else { this->exporter.writeUint32Raw(value); } return sizeof(uint32_t); } }; #endif wkutils/src/coords.cpp0000644000176200001440000001044514163201260014537 0ustar liggesusers #include "wk/geometry-handler.hpp" #include "wk/wkb-reader.hpp" #include "wk/wkt-streamer.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; class WKCoordinateCounter: public WKGeometryHandler { public: size_t nCoordinates; bool sepNA; bool firstGeom; WKCoordinateCounter(bool sepNA): nCoordinates(0), sepNA(sepNA), firstGeom(true) {} void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { bool isSimple = meta.geometryType == WKGeometryType::Point || meta.geometryType == WKGeometryType::LineString || meta.geometryType == WKGeometryType::Polygon; this->nCoordinates += this->sepNA && !this->firstGeom && (meta.size > 0) && isSimple; if ((meta.size > 0) && isSimple) { this->firstGeom = false; } } void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->nCoordinates += this->sepNA && ringId > 0; } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->nCoordinates++; } }; class WKCoordinateAssembler: public WKGeometryHandler { public: IntegerVector featureId; IntegerVector partId; IntegerVector ringId; NumericVector x; NumericVector y; NumericVector z; NumericVector m; WKCoordinateAssembler(size_t nCoordinates, bool sepNA): featureId(nCoordinates), partId(nCoordinates), ringId(nCoordinates), x(nCoordinates), y(nCoordinates), z(nCoordinates), m(nCoordinates), i(0), lastFeatureId(0), lastPartId(0), lastRingId(0), sepNA(sepNA), firstGeom(true) {} List assembleCoordinates() { return List::create( _["feature_id"] = this->featureId, _["part_id"] = this->partId, _["ring_id"] = this->ringId, _["x"] = this->x, _["y"] = this->y, _["z"] = this->z, _["m"] = this->m ); } protected: R_xlen_t i; int lastFeatureId; int lastPartId; int lastRingId; bool sepNA; bool firstGeom; void nextFeatureStart(size_t featureId) { this->lastFeatureId = featureId + 1; } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->lastPartId = this->lastPartId + 1; bool isSimple = meta.geometryType == WKGeometryType::Point || meta.geometryType == WKGeometryType::LineString || meta.geometryType == WKGeometryType::Polygon; if (this->sepNA && !this->firstGeom && (meta.size > 0) && isSimple) { this->writeNASep(); } if ((meta.size > 0) && isSimple) { this->firstGeom = false; } } void nextLinearRingStart(const WKGeometryMeta& meta, uint32_t size, uint32_t ringId) { this->lastRingId = this->lastRingId + 1; if (this->sepNA && ringId > 0) { this->writeNASep(); } } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->featureId[i] = this->lastFeatureId; this->partId[i] = this->lastPartId; this->ringId[i] = this->lastRingId; this->x[i] = coord.x; this->y[i] = coord.y; if (coord.hasZ) { this->z[i] = coord.z; } else { this->z[i] = NA_REAL; } if (coord.hasM) { this->m[i] = coord.m; } else { this->m[i] = NA_REAL; } this->i++; } void writeNASep() { this->featureId[i] = NA_INTEGER; this->partId[i] = NA_INTEGER; this->ringId[i] = NA_INTEGER; this->x[i] = NA_REAL; this->y[i] = NA_REAL; this->z[i] = NA_REAL; this->m[i] = NA_REAL; this->i++; } }; List cpp_coords_base(WKReader& reader, bool sepNA) { WKCoordinateCounter counter(sepNA); reader.setHandler(&counter); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } reader.reset(); WKCoordinateAssembler assembler(counter.nCoordinates, sepNA); reader.setHandler(&assembler); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } return assembler.assembleCoordinates(); } // [[Rcpp::export]] List cpp_coords_wkb(List wkb, bool sepNA) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); return cpp_coords_base(reader, sepNA); } // [[Rcpp::export]] List cpp_coords_wkt(CharacterVector wkt, bool sepNA) { WKCharacterVectorProvider provider(wkt); WKTStreamer reader(provider); return cpp_coords_base(reader, sepNA); } wkutils/src/ranges.cpp0000644000176200001440000001553414163201260014531 0ustar liggesusers #include "wk/geometry-handler.hpp" #include "wk/wkb-reader.hpp" #include "wk/wkt-streamer.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; // I'm sure there's a more compact way to do this double min_reg(double x1i, double x2i) { bool x1NA = NumericVector::is_na(x1i); bool x2NA = NumericVector::is_na(x2i); if (x1NA || x2NA) { return NA_REAL; } else { return std::min(x1i, x2i); } } double max_reg(double x1i, double x2i) { bool x1NA = NumericVector::is_na(x1i); bool x2NA = NumericVector::is_na(x2i); if (x1NA || x2NA) { return NA_REAL; } else { return std::max(x1i, x2i); } } double min_na_rm(double x1i, double x2i) { bool x1NA = NumericVector::is_na(x1i); bool x2NA = NumericVector::is_na(x2i); if (x1NA && x2NA) { return R_PosInf; } else if (x1NA) { return x2i; } else if (x2NA) { return x1i; } else { return std::min(x1i, x2i); } } double max_na_rm(double x1i, double x2i) { bool x1NA = NumericVector::is_na(x1i); bool x2NA = NumericVector::is_na(x2i); if (x1NA && x2NA) { return R_NegInf; } else if (x1NA) { return x2i; } else if (x2NA) { return x1i; } else { return std::max(x1i, x2i); } } double min_finite(double x1i, double x2i) { bool x1NA = NumericVector::is_na(x1i) || x1i == R_NegInf || x1i == R_PosInf; bool x2NA = NumericVector::is_na(x2i) || x2i == R_NegInf || x2i == R_PosInf; if (x1NA && x2NA) { return R_PosInf; } else if (x1NA) { return x2i; } else if (x2NA) { return x1i; } else { return std::min(x1i, x2i); } } double max_finite(double x1i, double x2i) { bool x1NA = NumericVector::is_na(x1i) || x1i == R_NegInf || x1i == R_PosInf; bool x2NA = NumericVector::is_na(x2i) || x2i == R_NegInf || x2i == R_PosInf; if (x1NA && x2NA) { return R_NegInf; } else if (x1NA) { return x2i; } else if (x2NA) { return x1i; } else { return std::max(x1i, x2i); } } class WKRangeCalculator: public WKGeometryHandler { public: double xmin; double ymin; double zmin; double mmin; double xmax; double ymax; double zmax; double mmax; WKRangeCalculator(bool naRm, bool onlyFinite): naRm(naRm), onlyFinite(onlyFinite) { this->reset(); } void reset() { this->xmin = R_PosInf; this->ymin = R_PosInf; this->zmin = R_PosInf; this->mmin = R_PosInf; this->xmax = R_NegInf; this->ymax = R_NegInf; this->zmax = R_NegInf; this->mmax = R_NegInf; } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { if (this->onlyFinite) { this->xmin = min_finite(this->xmin, coord.x); this->ymin = min_finite(this->ymin, coord.y); if (coord.hasZ) this->zmin = min_finite(this->zmin, coord.z); if (coord.hasM) this->mmin = min_finite(this->mmin, coord.m); this->xmax = max_finite(this->xmax, coord.x); this->ymax = max_finite(this->ymax, coord.y); if (coord.hasZ) this->zmax = max_finite(this->zmax, coord.z); if (coord.hasM) this->mmax = max_finite(this->zmin, coord.m); } else if (this->naRm) { this->xmin = min_na_rm(this->xmin, coord.x); this->ymin = min_na_rm(this->ymin, coord.y); if (coord.hasZ) this->zmin = min_na_rm(this->zmin, coord.z); if (coord.hasM) this->mmin = min_na_rm(this->mmin, coord.m); this->xmax = max_na_rm(this->xmax, coord.x); this->ymax = max_na_rm(this->ymax, coord.y); if (coord.hasZ) this->zmax = max_na_rm(this->zmax, coord.z); if (coord.hasM) this->mmax = max_na_rm(this->zmin, coord.m); } else { this->xmin = min_reg(this->xmin, coord.x); this->ymin = min_reg(this->ymin, coord.y); if (coord.hasZ) this->zmin = min_reg(this->zmin, coord.z); if (coord.hasM) this->mmin = min_reg(this->mmin, coord.m); this->xmax = max_reg(this->xmax, coord.x); this->ymax = max_reg(this->ymax, coord.y); if (coord.hasZ) this->zmax = max_reg(this->zmax, coord.z); if (coord.hasM) this->mmax = max_reg(this->zmin, coord.m); } } private: bool naRm; bool onlyFinite; }; class WKFeatureRangeCalculator: public WKRangeCalculator { public: NumericVector vxmin; NumericVector vymin; NumericVector vzmin; NumericVector vmmin; NumericVector vxmax; NumericVector vymax; NumericVector vzmax; NumericVector vmmax; WKFeatureRangeCalculator(size_t size, bool naRm, bool onlyFinite): WKRangeCalculator(naRm, onlyFinite), vxmin(size), vymin(size), vzmin(size), vmmin(size), vxmax(size), vymax(size), vzmax(size), vmmax(size) {} void nextFeatureStart(size_t featureId) { this->reset(); } void nextFeatureEnd(size_t featureId) { this->vxmin[featureId] = this->xmin; this->vymin[featureId] = this->ymin; this->vzmin[featureId] = this->zmin; this->vmmin[featureId] = this->mmin; this->vxmax[featureId] = this->xmax; this->vymax[featureId] = this->ymax; this->vzmax[featureId] = this->zmax; this->vmmax[featureId] = this->mmin; } }; List cpp_ranges_base(WKReader& reader, bool naRm, bool onlyFinite) { WKRangeCalculator ranges(naRm, onlyFinite); reader.setHandler(&ranges); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } return List::create( _["xmin"] = ranges.xmin, _["ymin"] = ranges.ymin, _["zmin"] = ranges.zmin, _["mmin"] = ranges.mmin, _["xmax"] = ranges.xmax, _["ymax"] = ranges.ymax, _["zmax"] = ranges.zmax, _["mmax"] = ranges.mmax ); } // [[Rcpp::export]] List cpp_ranges_wkb(List wkb, bool naRm, bool onlyFinite) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); return cpp_ranges_base(reader, naRm, onlyFinite); } // [[Rcpp::export]] List cpp_ranges_wkt(CharacterVector wkt, bool naRm, bool onlyFinite) { WKCharacterVectorProvider provider(wkt); WKTStreamer reader(provider); return cpp_ranges_base(reader, naRm, onlyFinite); } List cpp_feature_ranges_base(WKReader& reader, bool naRm, bool onlyFinite) { WKFeatureRangeCalculator ranges(reader.nFeatures(), naRm, onlyFinite); reader.setHandler(&ranges); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } return List::create( _["xmin"] = ranges.vxmin, _["ymin"] = ranges.vymin, _["zmin"] = ranges.vzmin, _["mmin"] = ranges.vmmin, _["xmax"] = ranges.vxmax, _["ymax"] = ranges.vymax, _["zmax"] = ranges.vzmax, _["mmax"] = ranges.vmmax ); } // [[Rcpp::export]] List cpp_feature_ranges_wkb(List wkb, bool naRm, bool onlyFinite) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); return cpp_feature_ranges_base(reader, naRm, onlyFinite); } // [[Rcpp::export]] List cpp_feature_ranges_wkt(CharacterVector wkt, bool naRm, bool onlyFinite) { WKCharacterVectorProvider provider(wkt); WKTStreamer reader(provider); return cpp_feature_ranges_base(reader, naRm, onlyFinite); } wkutils/src/finite.cpp0000644000176200001440000000606114163201260014523 0ustar liggesusers #include "wk/wkt-reader.hpp" #include "wk/wkb-reader.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; class WKHasSomethingException: public WKParseException { public: const static int CODE_HAS_SOMETHING = 2948379; WKHasSomethingException(): WKParseException(CODE_HAS_SOMETHING) {} }; class WKHasSomethingHandler: public WKGeometryHandler { public: LogicalVector output; WKHasSomethingHandler(size_t size): output(size) {} void nextFeatureStart(size_t featureId) { this->featureIsNull = false; } void nextNull(size_t featureId) { this->featureIsNull = true; } void nextFeatureEnd(size_t featureId) { if (this->featureIsNull) { this->output[featureId] = NA_LOGICAL; } else { this->output[featureId] = false; } } bool nextError(WKParseException& error, size_t featureId) { if (error.code() == WKHasSomethingException::CODE_HAS_SOMETHING) { this->output[featureId] = true; return true; } else { return false; } } private: bool featureIsNull; }; class WKHasNonFiniteHandler: public WKHasSomethingHandler { public: WKHasNonFiniteHandler(size_t size): WKHasSomethingHandler(size) {} void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { for (size_t i = 0; i < coord.size(); i++) { double value = coord[i]; if (!std::isfinite(value)) { throw WKHasSomethingException(); } } } }; class WKHasMissingHandler: public WKHasSomethingHandler { public: WKHasMissingHandler(size_t size): WKHasSomethingHandler(size) {} void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { for (size_t i = 0; i < coord.size(); i++) { double value = coord[i]; if (std::isnan(value)) { throw WKHasSomethingException(); } } } }; LogicalVector has_non_finite_base(WKReader& reader) { WKHasNonFiniteHandler handler(reader.nFeatures()); reader.setHandler(&handler); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } return handler.output; } // [[Rcpp::export]] LogicalVector cpp_wkt_has_non_finite(CharacterVector wkt) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); return has_non_finite_base(reader); } // [[Rcpp::export]] LogicalVector cpp_wkb_has_non_finite(List wkb) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); return has_non_finite_base(reader); } LogicalVector has_missing_base(WKReader& reader) { WKHasMissingHandler handler(reader.nFeatures()); reader.setHandler(&handler); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } return handler.output; } // [[Rcpp::export]] LogicalVector cpp_wkt_has_missing(CharacterVector wkt) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); return has_missing_base(reader); } // [[Rcpp::export]] LogicalVector cpp_wkb_has_missing(List wkb) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); return has_missing_base(reader); } wkutils/src/meta.cpp0000644000176200001440000000722114163201260014172 0ustar liggesusers #include #include "wk/geometry-handler.hpp" #include "wk/wkb-reader.hpp" #include "wk/wkt-reader.hpp" #include "wk/wkt-streamer.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; class WKMetaCounter: public WKGeometryHandler { public: size_t nMeta; WKMetaCounter(): nMeta(0) {} void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { this->nMeta++; } void nextNull(size_t featureId) { this->nMeta++; } }; class WKMetaAssembler: public WKGeometryHandler { public: IntegerVector featureId; IntegerVector partId; IntegerVector typeId; IntegerVector size; IntegerVector srid; LogicalVector hasZ; LogicalVector hasM; WKMetaAssembler(bool recursive, size_t nMeta): featureId(nMeta), partId(nMeta), typeId(nMeta), size(nMeta), srid(nMeta), hasZ(nMeta), hasM(nMeta), i(0), lastPartId(0), recursive(recursive) {} List assembleMeta() { return List::create( _["feature_id"] = this->featureId, _["part_id"] = this->partId, _["type_id"] = this->typeId, _["size"] = this->size, _["srid"] = this->srid, _["has_z"] = this->hasZ, _["has_m"] = this->hasM ); } void nextFeatureStart(size_t featureId) { this->lastFeatureId = featureId + 1; this->featureHasMeta = false; } void nextNull(size_t featureId) { this->featureId[i] = this->lastFeatureId; this->partId[i] = NA_INTEGER; this->typeId[i] = NA_INTEGER; this->size[i] = NA_INTEGER; this->srid[i] = NA_INTEGER; this->hasZ[i] = NA_LOGICAL; this->hasM[i] = NA_LOGICAL; this->i++; } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { if (!this->recursive && this->featureHasMeta) { return; } this->lastPartId = this->lastPartId + 1; this->featureId[i] = this->lastFeatureId; this->partId[i] = this->lastPartId; this->typeId[i] = meta.geometryType; if (meta.hasSize) { this->size[i] = meta.size; } else { this->size[i] = NA_INTEGER; } if (meta.hasSRID) { this->srid[i] = meta.srid; } else { this->srid[i] = NA_INTEGER; } this->hasZ[i] = meta.hasZ; this->hasM[i] = meta.hasM; this->i++; if (!this->recursive) { // much faster than using exceptions, with the added bonus that // we get the number of coordinates as well! this->featureHasMeta = true; } } protected: R_xlen_t i; int lastFeatureId; int lastPartId; bool recursive; bool featureHasMeta; }; List cpp_meta_base(WKReader& reader, bool recursive) { size_t nMeta; if (recursive) { WKMetaCounter counter; reader.setHandler(&counter); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } nMeta = counter.nMeta; reader.reset(); } else { nMeta = reader.nFeatures(); } WKMetaAssembler assembler(recursive, nMeta); reader.setHandler(&assembler); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } return assembler.assembleMeta(); } // [[Rcpp::export]] List cpp_meta_wkb(List wkb, bool recursive) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); return cpp_meta_base(reader, recursive); } // [[Rcpp::export]] List cpp_meta_wkt(CharacterVector wkt, bool recursive) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); return cpp_meta_base(reader, recursive); } // [[Rcpp::export]] List cpp_meta_wkt_streamer(CharacterVector wkt, bool recursive) { WKCharacterVectorProvider provider(wkt); WKTStreamer reader(provider); return cpp_meta_base(reader, recursive); } wkutils/src/coords-translate.cpp0000644000176200001440000000557514163201260016542 0ustar liggesusers #include #include "wk/rcpp-translate.hpp" #include "wk/rcpp-coord-reader.hpp" using namespace Rcpp; // [[Rcpp::export]] CharacterVector cpp_coords_point_translate_wkt(NumericVector x, NumericVector y, NumericVector z, NumericVector m, int precision, bool trim) { WKRcppPointCoordProvider provider(x, y, z, m); WKRcppPointCoordReader reader(provider); return wk::rcpp_translate_wkt(reader, precision, trim); } // [[Rcpp::export]] List cpp_coords_point_translate_wkb(NumericVector x, NumericVector y, NumericVector z, NumericVector m, int endian, int bufferSize) { WKRcppPointCoordProvider provider(x, y, z, m); WKRcppPointCoordReader reader(provider); return wk::rcpp_translate_wkb(reader, endian, bufferSize); } // [[Rcpp::export]] CharacterVector cpp_coords_linestring_translate_wkt(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, int precision, bool trim) { WKRcppLinestringCoordProvider provider(x, y, z, m, featureId); WKRcppLinestringCoordReader reader(provider); return wk::rcpp_translate_wkt(reader, precision, trim); } // [[Rcpp::export]] List cpp_coords_linestring_translate_wkb(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, int endian, int bufferSize) { WKRcppLinestringCoordProvider provider(x, y, z, m, featureId); WKRcppLinestringCoordReader reader(provider); return wk::rcpp_translate_wkb(reader, endian, bufferSize); } // [[Rcpp::export]] CharacterVector cpp_coords_polygon_translate_wkt(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, IntegerVector ringId, int precision, bool trim) { WKRcppPolygonCoordProvider provider(x, y, z, m, featureId, ringId); WKRcppPolygonCoordReader reader(provider); return wk::rcpp_translate_wkt(reader, precision, trim); } // [[Rcpp::export]] List cpp_coords_polygon_translate_wkb(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, IntegerVector ringId, int endian, int bufferSize) { WKRcppPolygonCoordProvider provider(x, y, z, m, featureId, ringId); WKRcppPolygonCoordReader reader(provider); return wk::rcpp_translate_wkb(reader, endian, bufferSize); } wkutils/src/Makevars0000644000176200001440000000004414320423117014232 0ustar liggesusersCXX_STD=CXX11 PKG_CPPFLAGS=-I../src wkutils/src/debug.cpp0000644000176200001440000000154614163201260014336 0ustar liggesusers #include "wk/geometry-debug-handler.hpp" #include "wk/wkb-reader.hpp" #include "wk/wkt-reader.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; void cpp_debug_base(WKReader& reader) { WKGeometryDebugHandler handler(Rcout); reader.setHandler(&handler); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } } // [[Rcpp::export]] void cpp_debug_wkb(List wkb) { WKRawVectorListProvider input(wkb); WKBReader reader(input); cpp_debug_base(reader); } // [[Rcpp::export]] void cpp_debug_wkt(CharacterVector input) { WKCharacterVectorProvider provider(input); WKTReader reader(provider); cpp_debug_base(reader); } // [[Rcpp::export]] void cpp_debug_wkt_streamer(CharacterVector input) { WKCharacterVectorProvider provider(input); WKTStreamer reader(provider); cpp_debug_base(reader); } wkutils/src/unnest.cpp0000644000176200001440000001364514163201260014567 0ustar liggesusers #include #include "wk/wkt-writer.hpp" #include "wk/wkt-reader.hpp" #include "wk/wkb-writer.hpp" #include "wk/wkb-reader.hpp" #include "wk/filter.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; class WKUnnester: public WKMetaFilter { public: WKUnnester(WKGeometryHandler& handler, bool keepEmpty, bool keepMulti, int maxUnnestDepth = INT_MAX): WKMetaFilter(handler), newFeatureId(0), topLevelMetaId(0), keepEmpty(keepEmpty), maxUnnestDepth(maxUnnestDepth), unnestDepth(0) { if (keepMulti) { this->minUnnestType = WKGeometryType::GeometryCollection; } else { this->minUnnestType = WKGeometryType::MultiPoint; } } bool shouldUnnest(const WKGeometryMeta& meta, int unnestDepth) { if (unnestDepth >= this->maxUnnestDepth) { return false; } else if (this->keepEmpty && (meta.size == 0)) { return false; } else if (meta.geometryType >= this->minUnnestType) { return true; } else { return false; } } // at the start, the unnestDepth is correct bool shouldUnnestStart(const WKGeometryMeta& meta) { if (this->shouldUnnest(meta, this->unnestDepth)) { this->isUnnested.insert(meta.id()); return true; } else { return false; } } // at the end, just check if this meta was unnested bool shouldUnnestEnd(const WKGeometryMeta& meta) { return this->isUnnested.count(meta.id()) == 1; } WKGeometryMeta newGeometryMeta(const WKGeometryMeta& meta, uint32_t partId) { if (this->unnestDepth > 0) { WKGeometryMeta newMeta(meta); newMeta.hasSRID = this->newHasSrid; newMeta.srid = this->newSrid; return newMeta; } else { return meta; } } void nextFeatureStart(size_t featureId) { this->isUnnested.clear(); this->topLevelMetaId = 0; this->unnestDepth = 0; } void nextFeatureEnd(size_t featureId) { } void nextNull(size_t featureId) { this->handler.nextFeatureStart(this->newFeatureId); this->handler.nextNull(this->newFeatureId); this->handler.nextFeatureEnd(this->newFeatureId); this->newFeatureId++; } void nextGeometryStart(const WKGeometryMeta& meta, uint32_t partId) { if (this->shouldUnnestStart(meta)) { if (this->unnestDepth == 0) { this->newHasSrid = meta.hasSRID; this->newSrid = meta.srid; } this->unnestDepth++; return; } if (this->topLevelMetaId == 0) { this->topLevelMetaId = meta.id(); partId = WKReader::PART_ID_NONE; this->handler.nextFeatureStart(this->newFeatureId); } // takes care of meta updating WKMetaFilter::nextGeometryStart(meta, partId); } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { this->handler.nextCoordinate(this->metaReplacement[meta.id()], coord, coordId); } void nextGeometryEnd(const WKGeometryMeta& meta, uint32_t partId) { if (this->shouldUnnestEnd(meta)) { this->isUnnested.erase(meta.id()); this->unnestDepth--; return; } if (meta.id() == this->topLevelMetaId) { this->handler.nextGeometryEnd(this->metaReplacement[meta.id()], WKReader::PART_ID_NONE); this->handler.nextFeatureEnd(this->newFeatureId); this->newFeatureId++; this->topLevelMetaId = 0; } else { this->handler.nextGeometryEnd(this->metaReplacement[meta.id()], partId); } // manually handled metaReplacement (no need for WKMetaFilter::nextGeometryEnd()) } private: size_t newFeatureId; uintptr_t topLevelMetaId; bool keepEmpty; int minUnnestType; int maxUnnestDepth; std::unordered_set isUnnested; int unnestDepth; bool newHasSrid; uint32_t newSrid; }; class WKUnnestedFeatureCounter: public WKGeometryHandler { public: size_t nNewFeatures; WKUnnestedFeatureCounter(): nNewFeatures(0) {} size_t reset() { size_t out = this->nNewFeatures; this->nNewFeatures = 0; return out; } void nextFeatureStart(size_t featureId) { this->nNewFeatures++; } }; IntegerVector unnest_count(WKReader& reader, bool keepEmpty, bool keepMulti, int maxUnnestDepth) { WKUnnestedFeatureCounter counter; WKUnnester unnester(counter, keepEmpty, keepMulti, maxUnnestDepth); reader.setHandler(&unnester); R_xlen_t featureId = 0; IntegerVector lengths(reader.nFeatures()); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); lengths[featureId] = counter.reset(); featureId++; } return lengths; } void unnest_do(WKReader& reader, WKWriter& writer, bool keepEmpty, bool keepMulti, int maxUnnestDepth) { WKUnnester unnester(writer, keepEmpty, keepMulti, maxUnnestDepth); reader.setHandler(&unnester); reader.reset(); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } } // [[Rcpp::export]] CharacterVector cpp_wkt_unnest(CharacterVector wkt, bool keepEmpty, bool keepMulti, int maxUnnestDepth) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); IntegerVector lengths = unnest_count(reader, keepEmpty, keepMulti, maxUnnestDepth); size_t nGeometries = sum(lengths); WKCharacterVectorExporter exporter(nGeometries); exporter.setRoundingPrecision(16); exporter.setTrim(true); WKTWriter writer(exporter); unnest_do(reader, writer, keepEmpty, keepMulti, maxUnnestDepth); exporter.output.attr("lengths") = lengths; return exporter.output; } // [[Rcpp::export]] List cpp_wkb_unnest(List wkb, bool keepEmpty, bool keepMulti, int maxUnnestDepth, int endian) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); IntegerVector lengths = unnest_count(reader, keepEmpty, keepMulti, maxUnnestDepth); size_t nGeometries = sum(lengths); WKRawVectorListExporter exporter(nGeometries); WKBWriter writer(exporter); writer.setEndian(endian); unnest_do(reader, writer, keepEmpty, keepMulti, maxUnnestDepth); exporter.output.attr("lengths") = lengths; return exporter.output; } wkutils/src/RcppExports.cpp0000644000176200001440000005333314357667367015577 0ustar liggesusers// Generated by using Rcpp::compileAttributes() -> do not edit by hand // Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 #include using namespace Rcpp; #ifdef RCPP_USE_GLOBAL_ROSTREAM Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); #endif // cpp_coords_point_translate_wkt CharacterVector cpp_coords_point_translate_wkt(NumericVector x, NumericVector y, NumericVector z, NumericVector m, int precision, bool trim); RcppExport SEXP _wkutils_cpp_coords_point_translate_wkt(SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP mSEXP, SEXP precisionSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< NumericVector >::type m(mSEXP); Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_point_translate_wkt(x, y, z, m, precision, trim)); return rcpp_result_gen; END_RCPP } // cpp_coords_point_translate_wkb List cpp_coords_point_translate_wkb(NumericVector x, NumericVector y, NumericVector z, NumericVector m, int endian, int bufferSize); RcppExport SEXP _wkutils_cpp_coords_point_translate_wkb(SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP mSEXP, SEXP endianSEXP, SEXP bufferSizeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< NumericVector >::type m(mSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); Rcpp::traits::input_parameter< int >::type bufferSize(bufferSizeSEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_point_translate_wkb(x, y, z, m, endian, bufferSize)); return rcpp_result_gen; END_RCPP } // cpp_coords_linestring_translate_wkt CharacterVector cpp_coords_linestring_translate_wkt(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, int precision, bool trim); RcppExport SEXP _wkutils_cpp_coords_linestring_translate_wkt(SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP mSEXP, SEXP featureIdSEXP, SEXP precisionSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< NumericVector >::type m(mSEXP); Rcpp::traits::input_parameter< IntegerVector >::type featureId(featureIdSEXP); Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_linestring_translate_wkt(x, y, z, m, featureId, precision, trim)); return rcpp_result_gen; END_RCPP } // cpp_coords_linestring_translate_wkb List cpp_coords_linestring_translate_wkb(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, int endian, int bufferSize); RcppExport SEXP _wkutils_cpp_coords_linestring_translate_wkb(SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP mSEXP, SEXP featureIdSEXP, SEXP endianSEXP, SEXP bufferSizeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< NumericVector >::type m(mSEXP); Rcpp::traits::input_parameter< IntegerVector >::type featureId(featureIdSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); Rcpp::traits::input_parameter< int >::type bufferSize(bufferSizeSEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_linestring_translate_wkb(x, y, z, m, featureId, endian, bufferSize)); return rcpp_result_gen; END_RCPP } // cpp_coords_polygon_translate_wkt CharacterVector cpp_coords_polygon_translate_wkt(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, IntegerVector ringId, int precision, bool trim); RcppExport SEXP _wkutils_cpp_coords_polygon_translate_wkt(SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP mSEXP, SEXP featureIdSEXP, SEXP ringIdSEXP, SEXP precisionSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< NumericVector >::type m(mSEXP); Rcpp::traits::input_parameter< IntegerVector >::type featureId(featureIdSEXP); Rcpp::traits::input_parameter< IntegerVector >::type ringId(ringIdSEXP); Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_polygon_translate_wkt(x, y, z, m, featureId, ringId, precision, trim)); return rcpp_result_gen; END_RCPP } // cpp_coords_polygon_translate_wkb List cpp_coords_polygon_translate_wkb(NumericVector x, NumericVector y, NumericVector z, NumericVector m, IntegerVector featureId, IntegerVector ringId, int endian, int bufferSize); RcppExport SEXP _wkutils_cpp_coords_polygon_translate_wkb(SEXP xSEXP, SEXP ySEXP, SEXP zSEXP, SEXP mSEXP, SEXP featureIdSEXP, SEXP ringIdSEXP, SEXP endianSEXP, SEXP bufferSizeSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< NumericVector >::type x(xSEXP); Rcpp::traits::input_parameter< NumericVector >::type y(ySEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< NumericVector >::type m(mSEXP); Rcpp::traits::input_parameter< IntegerVector >::type featureId(featureIdSEXP); Rcpp::traits::input_parameter< IntegerVector >::type ringId(ringIdSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); Rcpp::traits::input_parameter< int >::type bufferSize(bufferSizeSEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_polygon_translate_wkb(x, y, z, m, featureId, ringId, endian, bufferSize)); return rcpp_result_gen; END_RCPP } // cpp_coords_wkb List cpp_coords_wkb(List wkb, bool sepNA); RcppExport SEXP _wkutils_cpp_coords_wkb(SEXP wkbSEXP, SEXP sepNASEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< bool >::type sepNA(sepNASEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_wkb(wkb, sepNA)); return rcpp_result_gen; END_RCPP } // cpp_coords_wkt List cpp_coords_wkt(CharacterVector wkt, bool sepNA); RcppExport SEXP _wkutils_cpp_coords_wkt(SEXP wktSEXP, SEXP sepNASEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< bool >::type sepNA(sepNASEXP); rcpp_result_gen = Rcpp::wrap(cpp_coords_wkt(wkt, sepNA)); return rcpp_result_gen; END_RCPP } // cpp_debug_wkb void cpp_debug_wkb(List wkb); RcppExport SEXP _wkutils_cpp_debug_wkb(SEXP wkbSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); cpp_debug_wkb(wkb); return R_NilValue; END_RCPP } // cpp_debug_wkt void cpp_debug_wkt(CharacterVector input); RcppExport SEXP _wkutils_cpp_debug_wkt(SEXP inputSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type input(inputSEXP); cpp_debug_wkt(input); return R_NilValue; END_RCPP } // cpp_debug_wkt_streamer void cpp_debug_wkt_streamer(CharacterVector input); RcppExport SEXP _wkutils_cpp_debug_wkt_streamer(SEXP inputSEXP) { BEGIN_RCPP Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type input(inputSEXP); cpp_debug_wkt_streamer(input); return R_NilValue; END_RCPP } // cpp_wkt_set_srid CharacterVector cpp_wkt_set_srid(CharacterVector wkt, IntegerVector srid, int precision, bool trim); RcppExport SEXP _wkutils_cpp_wkt_set_srid(SEXP wktSEXP, SEXP sridSEXP, SEXP precisionSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< IntegerVector >::type srid(sridSEXP); Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkt_set_srid(wkt, srid, precision, trim)); return rcpp_result_gen; END_RCPP } // cpp_wkb_set_srid List cpp_wkb_set_srid(List wkb, IntegerVector srid, int endian); RcppExport SEXP _wkutils_cpp_wkb_set_srid(SEXP wkbSEXP, SEXP sridSEXP, SEXP endianSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< IntegerVector >::type srid(sridSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkb_set_srid(wkb, srid, endian)); return rcpp_result_gen; END_RCPP } // cpp_wkt_set_z CharacterVector cpp_wkt_set_z(CharacterVector wkt, NumericVector z, int precision, bool trim); RcppExport SEXP _wkutils_cpp_wkt_set_z(SEXP wktSEXP, SEXP zSEXP, SEXP precisionSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkt_set_z(wkt, z, precision, trim)); return rcpp_result_gen; END_RCPP } // cpp_wkb_set_z List cpp_wkb_set_z(List wkb, NumericVector z, int endian); RcppExport SEXP _wkutils_cpp_wkb_set_z(SEXP wkbSEXP, SEXP zSEXP, SEXP endianSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< NumericVector >::type z(zSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkb_set_z(wkb, z, endian)); return rcpp_result_gen; END_RCPP } // cpp_wkt_transform CharacterVector cpp_wkt_transform(CharacterVector wkt, NumericVector transform, int precision, bool trim); RcppExport SEXP _wkutils_cpp_wkt_transform(SEXP wktSEXP, SEXP transformSEXP, SEXP precisionSEXP, SEXP trimSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< NumericVector >::type transform(transformSEXP); Rcpp::traits::input_parameter< int >::type precision(precisionSEXP); Rcpp::traits::input_parameter< bool >::type trim(trimSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkt_transform(wkt, transform, precision, trim)); return rcpp_result_gen; END_RCPP } // cpp_wkb_transform List cpp_wkb_transform(List wkb, NumericVector transform, int endian); RcppExport SEXP _wkutils_cpp_wkb_transform(SEXP wkbSEXP, SEXP transformSEXP, SEXP endianSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< NumericVector >::type transform(transformSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkb_transform(wkb, transform, endian)); return rcpp_result_gen; END_RCPP } // cpp_wkt_has_non_finite LogicalVector cpp_wkt_has_non_finite(CharacterVector wkt); RcppExport SEXP _wkutils_cpp_wkt_has_non_finite(SEXP wktSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkt_has_non_finite(wkt)); return rcpp_result_gen; END_RCPP } // cpp_wkb_has_non_finite LogicalVector cpp_wkb_has_non_finite(List wkb); RcppExport SEXP _wkutils_cpp_wkb_has_non_finite(SEXP wkbSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkb_has_non_finite(wkb)); return rcpp_result_gen; END_RCPP } // cpp_wkt_has_missing LogicalVector cpp_wkt_has_missing(CharacterVector wkt); RcppExport SEXP _wkutils_cpp_wkt_has_missing(SEXP wktSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkt_has_missing(wkt)); return rcpp_result_gen; END_RCPP } // cpp_wkb_has_missing LogicalVector cpp_wkb_has_missing(List wkb); RcppExport SEXP _wkutils_cpp_wkb_has_missing(SEXP wkbSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkb_has_missing(wkb)); return rcpp_result_gen; END_RCPP } // cpp_meta_wkb List cpp_meta_wkb(List wkb, bool recursive); RcppExport SEXP _wkutils_cpp_meta_wkb(SEXP wkbSEXP, SEXP recursiveSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< bool >::type recursive(recursiveSEXP); rcpp_result_gen = Rcpp::wrap(cpp_meta_wkb(wkb, recursive)); return rcpp_result_gen; END_RCPP } // cpp_meta_wkt List cpp_meta_wkt(CharacterVector wkt, bool recursive); RcppExport SEXP _wkutils_cpp_meta_wkt(SEXP wktSEXP, SEXP recursiveSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< bool >::type recursive(recursiveSEXP); rcpp_result_gen = Rcpp::wrap(cpp_meta_wkt(wkt, recursive)); return rcpp_result_gen; END_RCPP } // cpp_meta_wkt_streamer List cpp_meta_wkt_streamer(CharacterVector wkt, bool recursive); RcppExport SEXP _wkutils_cpp_meta_wkt_streamer(SEXP wktSEXP, SEXP recursiveSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< bool >::type recursive(recursiveSEXP); rcpp_result_gen = Rcpp::wrap(cpp_meta_wkt_streamer(wkt, recursive)); return rcpp_result_gen; END_RCPP } // cpp_ranges_wkb List cpp_ranges_wkb(List wkb, bool naRm, bool onlyFinite); RcppExport SEXP _wkutils_cpp_ranges_wkb(SEXP wkbSEXP, SEXP naRmSEXP, SEXP onlyFiniteSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); Rcpp::traits::input_parameter< bool >::type onlyFinite(onlyFiniteSEXP); rcpp_result_gen = Rcpp::wrap(cpp_ranges_wkb(wkb, naRm, onlyFinite)); return rcpp_result_gen; END_RCPP } // cpp_ranges_wkt List cpp_ranges_wkt(CharacterVector wkt, bool naRm, bool onlyFinite); RcppExport SEXP _wkutils_cpp_ranges_wkt(SEXP wktSEXP, SEXP naRmSEXP, SEXP onlyFiniteSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); Rcpp::traits::input_parameter< bool >::type onlyFinite(onlyFiniteSEXP); rcpp_result_gen = Rcpp::wrap(cpp_ranges_wkt(wkt, naRm, onlyFinite)); return rcpp_result_gen; END_RCPP } // cpp_feature_ranges_wkb List cpp_feature_ranges_wkb(List wkb, bool naRm, bool onlyFinite); RcppExport SEXP _wkutils_cpp_feature_ranges_wkb(SEXP wkbSEXP, SEXP naRmSEXP, SEXP onlyFiniteSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); Rcpp::traits::input_parameter< bool >::type onlyFinite(onlyFiniteSEXP); rcpp_result_gen = Rcpp::wrap(cpp_feature_ranges_wkb(wkb, naRm, onlyFinite)); return rcpp_result_gen; END_RCPP } // cpp_feature_ranges_wkt List cpp_feature_ranges_wkt(CharacterVector wkt, bool naRm, bool onlyFinite); RcppExport SEXP _wkutils_cpp_feature_ranges_wkt(SEXP wktSEXP, SEXP naRmSEXP, SEXP onlyFiniteSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< bool >::type naRm(naRmSEXP); Rcpp::traits::input_parameter< bool >::type onlyFinite(onlyFiniteSEXP); rcpp_result_gen = Rcpp::wrap(cpp_feature_ranges_wkt(wkt, naRm, onlyFinite)); return rcpp_result_gen; END_RCPP } // cpp_wkt_unnest CharacterVector cpp_wkt_unnest(CharacterVector wkt, bool keepEmpty, bool keepMulti, int maxUnnestDepth); RcppExport SEXP _wkutils_cpp_wkt_unnest(SEXP wktSEXP, SEXP keepEmptySEXP, SEXP keepMultiSEXP, SEXP maxUnnestDepthSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< CharacterVector >::type wkt(wktSEXP); Rcpp::traits::input_parameter< bool >::type keepEmpty(keepEmptySEXP); Rcpp::traits::input_parameter< bool >::type keepMulti(keepMultiSEXP); Rcpp::traits::input_parameter< int >::type maxUnnestDepth(maxUnnestDepthSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkt_unnest(wkt, keepEmpty, keepMulti, maxUnnestDepth)); return rcpp_result_gen; END_RCPP } // cpp_wkb_unnest List cpp_wkb_unnest(List wkb, bool keepEmpty, bool keepMulti, int maxUnnestDepth, int endian); RcppExport SEXP _wkutils_cpp_wkb_unnest(SEXP wkbSEXP, SEXP keepEmptySEXP, SEXP keepMultiSEXP, SEXP maxUnnestDepthSEXP, SEXP endianSEXP) { BEGIN_RCPP Rcpp::RObject rcpp_result_gen; Rcpp::RNGScope rcpp_rngScope_gen; Rcpp::traits::input_parameter< List >::type wkb(wkbSEXP); Rcpp::traits::input_parameter< bool >::type keepEmpty(keepEmptySEXP); Rcpp::traits::input_parameter< bool >::type keepMulti(keepMultiSEXP); Rcpp::traits::input_parameter< int >::type maxUnnestDepth(maxUnnestDepthSEXP); Rcpp::traits::input_parameter< int >::type endian(endianSEXP); rcpp_result_gen = Rcpp::wrap(cpp_wkb_unnest(wkb, keepEmpty, keepMulti, maxUnnestDepth, endian)); return rcpp_result_gen; END_RCPP } static const R_CallMethodDef CallEntries[] = { {"_wkutils_cpp_coords_point_translate_wkt", (DL_FUNC) &_wkutils_cpp_coords_point_translate_wkt, 6}, {"_wkutils_cpp_coords_point_translate_wkb", (DL_FUNC) &_wkutils_cpp_coords_point_translate_wkb, 6}, {"_wkutils_cpp_coords_linestring_translate_wkt", (DL_FUNC) &_wkutils_cpp_coords_linestring_translate_wkt, 7}, {"_wkutils_cpp_coords_linestring_translate_wkb", (DL_FUNC) &_wkutils_cpp_coords_linestring_translate_wkb, 7}, {"_wkutils_cpp_coords_polygon_translate_wkt", (DL_FUNC) &_wkutils_cpp_coords_polygon_translate_wkt, 8}, {"_wkutils_cpp_coords_polygon_translate_wkb", (DL_FUNC) &_wkutils_cpp_coords_polygon_translate_wkb, 8}, {"_wkutils_cpp_coords_wkb", (DL_FUNC) &_wkutils_cpp_coords_wkb, 2}, {"_wkutils_cpp_coords_wkt", (DL_FUNC) &_wkutils_cpp_coords_wkt, 2}, {"_wkutils_cpp_debug_wkb", (DL_FUNC) &_wkutils_cpp_debug_wkb, 1}, {"_wkutils_cpp_debug_wkt", (DL_FUNC) &_wkutils_cpp_debug_wkt, 1}, {"_wkutils_cpp_debug_wkt_streamer", (DL_FUNC) &_wkutils_cpp_debug_wkt_streamer, 1}, {"_wkutils_cpp_wkt_set_srid", (DL_FUNC) &_wkutils_cpp_wkt_set_srid, 4}, {"_wkutils_cpp_wkb_set_srid", (DL_FUNC) &_wkutils_cpp_wkb_set_srid, 3}, {"_wkutils_cpp_wkt_set_z", (DL_FUNC) &_wkutils_cpp_wkt_set_z, 4}, {"_wkutils_cpp_wkb_set_z", (DL_FUNC) &_wkutils_cpp_wkb_set_z, 3}, {"_wkutils_cpp_wkt_transform", (DL_FUNC) &_wkutils_cpp_wkt_transform, 4}, {"_wkutils_cpp_wkb_transform", (DL_FUNC) &_wkutils_cpp_wkb_transform, 3}, {"_wkutils_cpp_wkt_has_non_finite", (DL_FUNC) &_wkutils_cpp_wkt_has_non_finite, 1}, {"_wkutils_cpp_wkb_has_non_finite", (DL_FUNC) &_wkutils_cpp_wkb_has_non_finite, 1}, {"_wkutils_cpp_wkt_has_missing", (DL_FUNC) &_wkutils_cpp_wkt_has_missing, 1}, {"_wkutils_cpp_wkb_has_missing", (DL_FUNC) &_wkutils_cpp_wkb_has_missing, 1}, {"_wkutils_cpp_meta_wkb", (DL_FUNC) &_wkutils_cpp_meta_wkb, 2}, {"_wkutils_cpp_meta_wkt", (DL_FUNC) &_wkutils_cpp_meta_wkt, 2}, {"_wkutils_cpp_meta_wkt_streamer", (DL_FUNC) &_wkutils_cpp_meta_wkt_streamer, 2}, {"_wkutils_cpp_ranges_wkb", (DL_FUNC) &_wkutils_cpp_ranges_wkb, 3}, {"_wkutils_cpp_ranges_wkt", (DL_FUNC) &_wkutils_cpp_ranges_wkt, 3}, {"_wkutils_cpp_feature_ranges_wkb", (DL_FUNC) &_wkutils_cpp_feature_ranges_wkb, 3}, {"_wkutils_cpp_feature_ranges_wkt", (DL_FUNC) &_wkutils_cpp_feature_ranges_wkt, 3}, {"_wkutils_cpp_wkt_unnest", (DL_FUNC) &_wkutils_cpp_wkt_unnest, 4}, {"_wkutils_cpp_wkb_unnest", (DL_FUNC) &_wkutils_cpp_wkb_unnest, 5}, {NULL, NULL, 0} }; RcppExport void R_init_wkutils(DllInfo *dll) { R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); R_useDynamicSymbols(dll, FALSE); } wkutils/src/filters.cpp0000644000176200001440000001271414163201260014717 0ustar liggesusers #include "wk/wkt-writer.hpp" #include "wk/wkt-reader.hpp" #include "wk/wkb-writer.hpp" #include "wk/wkb-reader.hpp" #include "wk/filter.hpp" #include #include "wk/rcpp-io.hpp" using namespace Rcpp; // --------- srid ------------- class WKSetSridFilter: public WKMetaFilter { public: WKSetSridFilter(WKGeometryHandler& handler, IntegerVector srid): WKMetaFilter(handler), srid(srid), featureSrid(NA_INTEGER) {} void nextFeatureStart(size_t featureId) { this->featureSrid = this->srid[featureId]; WKMetaFilter::nextFeatureStart(featureId); } WKGeometryMeta newGeometryMeta(const WKGeometryMeta& meta, uint32_t partId) { WKGeometryMeta newMeta(meta); if (IntegerVector::is_na(this->featureSrid)) { newMeta.hasSRID = false; } else { newMeta.hasSRID = true; newMeta.srid = this->featureSrid; } return newMeta; } private: IntegerVector srid; int featureSrid; }; void set_srid_base(WKReader& reader, WKWriter& writer, IntegerVector srid) { WKSetSridFilter filter(writer, srid); reader.setHandler(&filter); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } } // [[Rcpp::export]] CharacterVector cpp_wkt_set_srid(CharacterVector wkt, IntegerVector srid, int precision = 16, bool trim = true) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); WKCharacterVectorExporter exporter(wkt.size()); WKTWriter writer(exporter); exporter.setRoundingPrecision(precision); exporter.setTrim(trim); set_srid_base(reader, writer, srid); return exporter.output; } // [[Rcpp::export]] List cpp_wkb_set_srid(List wkb, IntegerVector srid, int endian) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); WKRawVectorListExporter exporter(wkb.size()); WKBWriter writer(exporter); writer.setEndian(endian); set_srid_base(reader, writer, srid); return exporter.output; } // ----------- set z ------------- class WKSetZFilter: public WKMetaFilter { public: WKSetZFilter(WKGeometryHandler& handler, NumericVector z): WKMetaFilter(handler), z(z), featureZ(NA_REAL) {} void nextFeatureStart(size_t featureId) { this->featureZ = this->z[featureId]; WKMetaFilter::nextFeatureStart(featureId); } WKGeometryMeta newGeometryMeta(const WKGeometryMeta& meta, uint32_t partId) { WKGeometryMeta newMeta(meta); newMeta.hasZ = !NumericVector::is_na(this->featureZ); return newMeta; } void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { WKCoord newCoord(coord); newCoord.z = this->featureZ; newCoord.hasZ = !NumericVector::is_na(this->featureZ); WKMetaFilter::nextCoordinate(meta, newCoord, coordId); } private: NumericVector z; double featureZ; }; void set_z_base(WKReader& reader, WKWriter& writer, NumericVector z) { WKSetZFilter filter(writer, z); reader.setHandler(&filter); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } } // [[Rcpp::export]] CharacterVector cpp_wkt_set_z(CharacterVector wkt, NumericVector z, int precision = 16, bool trim = true) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); WKCharacterVectorExporter exporter(wkt.size()); WKTWriter writer(exporter); exporter.setRoundingPrecision(precision); exporter.setTrim(trim); set_z_base(reader, writer, z); return exporter.output; } // [[Rcpp::export]] List cpp_wkb_set_z(List wkb, NumericVector z, int endian) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); WKRawVectorListExporter exporter(wkb.size()); WKBWriter writer(exporter); writer.setEndian(endian); set_z_base(reader, writer, z); return exporter.output; } // ---------- transform ----------- class WKTransformFilter: public WKFilter { public: // here, t is the 6-member affine transform in column-major format // (e.g., [1 0 0 1 tx ty]) WKTransformFilter(WKGeometryHandler& handler, NumericVector t): WKFilter(handler), t1(t[0]), t2(t[1]), t3(t[2]), t4(t[3]), t5(t[4]), t6(t[5]) {} void nextCoordinate(const WKGeometryMeta& meta, const WKCoord& coord, uint32_t coordId) { WKCoord newCoord(coord); newCoord.x = t1 * coord.x + t3 * coord.y + t5; newCoord.y = t2 * coord.x + t4 * coord.y + t6; WKFilter::nextCoordinate(meta, newCoord, coordId); } private: double t1, t2, t3, t4, t5, t6; }; void transform_base(WKReader& reader, WKWriter& writer, NumericVector transform) { WKTransformFilter filter(writer, transform); reader.setHandler(&filter); while (reader.hasNextFeature()) { checkUserInterrupt(); reader.iterateFeature(); } } // [[Rcpp::export]] CharacterVector cpp_wkt_transform(CharacterVector wkt, NumericVector transform, int precision = 16, bool trim = true) { WKCharacterVectorProvider provider(wkt); WKTReader reader(provider); WKCharacterVectorExporter exporter(wkt.size()); WKTWriter writer(exporter); exporter.setRoundingPrecision(precision); exporter.setTrim(trim); transform_base(reader, writer, transform); return exporter.output; } // [[Rcpp::export]] List cpp_wkb_transform(List wkb, NumericVector transform, int endian) { WKRawVectorListProvider provider(wkb); WKBReader reader(provider); WKRawVectorListExporter exporter(wkb.size()); WKBWriter writer(exporter); writer.setEndian(endian); transform_base(reader, writer, transform); return exporter.output; } wkutils/R/0000755000176200001440000000000014357667367012205 5ustar liggesuserswkutils/R/unnest.R0000644000176200001440000000210414163201260013604 0ustar liggesusers #' Flatten nested geometry structures #' #' @inheritParams wk::wkb_translate_wkt #' @param keep_empty If `TRUE`, a GEOMETRYCOLLECTION EMPTY is left as-is #' rather than collapsing to length 0. #' @param keep_multi If `TRUE`, MULTI* geometries are not expanded to sub-features. #' @param max_depth The maximum recursive GEOMETRYCOLLECTION depth to unnest. #' #' @return An unclassed vector with attribute `lengths`, which is an integer vector #' with the same length as the input denoting the length to which each #' feature was expanded. #' @export #' #' @examples #' wkt_unnest("GEOMETRYCOLLECTION (POINT (1 2), POINT (3 4))") #' wkt_unnest("GEOMETRYCOLLECTION EMPTY") #' wkt_unnest("GEOMETRYCOLLECTION EMPTY", keep_empty = TRUE) #' wkt_unnest <- function(wkt, keep_empty = FALSE, keep_multi = TRUE, max_depth = 1) { cpp_wkt_unnest(wkt, keep_empty, keep_multi, max_depth) } #' @rdname wkt_unnest #' @export wkb_unnest <- function(wkb, keep_empty = FALSE, keep_multi = TRUE, max_depth = 1) { cpp_wkb_unnest(wkb, keep_empty, keep_multi, max_depth, endian = wk_platform_endian()) } wkutils/R/utils.R0000644000176200001440000000023114163201260013427 0ustar liggesusers new_data_frame <- function(x, nrow = length(x[[1]])) { tibble::new_tibble(x, nrow = nrow) } `%||%` <- function (x, y) { if (is.null(x)) y else x } wkutils/R/coords.R0000644000176200001440000000273314163201260013571 0ustar liggesusers #' Extract coordinates from well-known geometries #' #' These functions are optimised for graphics output, #' which in R require flat coordinate structures. See #' [graphics::points()], [graphics::lines()], #' and [graphics::polypath()] for how to send these #' to a graphics device, or [grid::pointsGrob()], #' [grid::linesGrob()], and [grid::pathGrob()] for how #' to create graphical objects using this output. #' #' @inheritParams wk::wkb_translate_wkt #' @param sep_na Use `TRUE` to separate geometries and linear #' rings with a row of `NA`s. This is useful for generating #' output that can be fed directly to [graphics::polypath()] #' or [graphics::lines()] without modification. #' #' @return A data.frame with columns: #' - `feature_id`: The index of the top-level feature #' - `part_id`: The part identifier, guaranteed to be unique for every simple geometry #' (including those contained within a multi-geometry or collection) #' - `ring_id`: The ring identifier, guaranteed to be unique for every ring. #' - `x`, `y`, `z`, `m`: Coordinaate values (both absence and `nan` are recorded #' as `NA`) #' #' @export #' #' @examples #' text <- c("LINESTRING (0 1, 19 27)", "LINESTRING (-1 -1, 4 10)") #' wkt_coords(text) #' wkt_coords(text, sep_na = TRUE) #' wkb_coords <- function(wkb, sep_na = FALSE) { new_data_frame(cpp_coords_wkb(wkb, sep_na)) } #' @rdname wkb_coords #' @export wkt_coords <- function(wkt, sep_na = FALSE) { new_data_frame(cpp_coords_wkt(wkt, sep_na)) } wkutils/R/grob.R0000644000176200001440000001403714163201260013231 0ustar liggesusers #' Generate grid geometries from well-known geometries #' #' Using [wkt_meta()] and [wkt_coords()], these functions create graphical objects #' using the grid package. Vectors that contain geometries of a single dimension #' are efficiently packed into a [grid::pointsGrob()], [grid::polylineGrob()], #' or [grid::pathGrob()]. Vectors with mixed types and nested collections are encoded #' less efficiently using a [grid::gTree()]. #' #' @inheritParams wk::wkb_translate_wkt #' @param ... Graphical parameters passed to [grid::gpar()]. These are recycled along #' the input. Dynamic dots (e.g., `!!!`) are supported. #' @param rule Use "winding" if polygon rings are correctly encoded with a winding #' direction. #' @param default.units Coordinate units, which may be defined by the viewport (see #' [grid::unit()]). Defaults to native. #' @param name,vp Passed to [grid::pointsGrob()], [grid::polylineGrob()], #' [grid::pathGrob()], or [grid::gTree()] depending on the types of geometries #' in the input. #' #' @return A [graphical object][grid::grob] #' @export #' #' @examples #' grid::grid.newpage() #' grid::grid.draw(wkt_grob("POINT (0.5 0.5)", pch = 16, default.units = "npc")) #' wkt_grob <- function(wkt, ..., rule = "evenodd", default.units = "native", name = NULL, vp = NULL) { grob_wk_possibly_nested( wkt, ..., unnest_fun = wkt_unnest, meta_fun = wkt_meta, coords_fun = wkt_coords, rule = rule, default.units = default.units, name = name, vp = vp ) } #' @rdname wkt_grob #' @export wkb_grob <- function(wkt, ..., rule = "evenodd", default.units = "native", name = NULL, vp = NULL) { grob_wk_possibly_nested( wkt, ..., unnest_fun = wkb_unnest, meta_fun = wkb_meta, coords_fun = wkb_coords, rule = rule, default.units = default.units, name = name, vp = vp ) } grob_wk_possibly_nested <- function(x, ..., unnest_fun, meta_fun, coords_fun, rule, default.units, name, vp) { meta <- meta_fun(x) gpar_values_all <- vctrs::vec_recycle_common(..., .size = length(x)) # if there are any collections, unnest everything if (any((meta$size > 0) & (meta$type_id == 7))) { unnested <- unnest_fun(x, keep_empty = FALSE, keep_multi = TRUE, max_depth = 10) lengths <- attr(unnested, "lengths") run_length_enc <- structure( list(lengths = lengths, values = seq_along(lengths)), class = "rle" ) unnested_gpar <- lapply(gpar_values_all, "[", inverse.rle(run_length_enc)) return( wkt_grob( unnested, !!!unnested_gpar, rule = rule, default.units = default.units, name = name, vp = vp ) ) } coords <- coords_fun(x) # grid doesn't do zero-length input, so if there are no coordinates, return # an empty grob if (nrow(coords) == 0) { return(grid::gTree(name = name, vp = vp, children = grid::gList())) } grob_wk_base( meta, coords, gpar_values_all, rule = rule, default.units = default.units, name = name, vp = vp ) } grob_wk_base <- function(meta, coords, gpar_values_all, rule, default.units, name = NULL, vp = NULL) { # use meta to try to create the most efficient grob possible non_empty_types <- meta$type_id[meta$size > 0] # non-empty IDs are used by mixed types and polygons non_empty_features <- unique(meta$feature_id[meta$size > 0]) if (all(non_empty_types %in% c(1, 4))) { # Need to get a tiny bit creative here, because pointsGrob # doesn't have a pathId argument like pathGrob. The key here is # to select gpar_values_all so that its rows match the # number of actual part_id values for each feature part_lengths <- rle(coords$part_id) part_row_start <- c(0, cumsum(part_lengths$lengths)) + 1 part_feature_ids <- coords$feature_id[part_row_start[-length(part_row_start)]] gpar_values_all <- lapply(gpar_values_all, "[", part_feature_ids) pch <- gpar_values_all$pch %||% 1 size <- gpar_values_all$size %||% grid::unit(1, "char") gpar_values_all$pch <- NULL gpar_values_all$size <- NULL grid::pointsGrob( x = coords$x, y = coords$y, pch = pch, size = size, gp = do.call(grid::gpar, gpar_values_all), default.units = default.units, name = name, vp = vp ) } else if (all(non_empty_types %in% c(2, 5))) { # Need to get a tiny bit creative here, because polylineGrob # doesn't have a pathId argument like pathGrob. The key here is # to select gpar_values_all so that its rows match the # number of actual part_id values for each feature part_lengths <- rle(coords$part_id) part_row_start <- c(0, cumsum(part_lengths$lengths)) + 1 part_feature_ids <- coords$feature_id[part_row_start[-length(part_row_start)]] gpar_values_all <- lapply(gpar_values_all, "[", part_feature_ids) grid::polylineGrob( x = coords$x, y = coords$y, id.lengths = part_lengths$lengths, gp = do.call(grid::gpar, gpar_values_all), default.units = default.units, name = name, vp = vp ) } else if (all(non_empty_types %in% c(3, 6))) { non_empty_gpar_values <- lapply(gpar_values_all, "[", non_empty_features) grid::pathGrob( x = coords$x, y = coords$y, id = coords$ring_id, pathId = coords$feature_id, gp = do.call(grid::gpar, non_empty_gpar_values), default.units = default.units, name = name, vp = vp, rule = rule ) } else if (all(non_empty_types != 7)) { # Mixed input, but no collections (collections should be handled by unnest()) # not very efficient, but better than failing grobs <- lapply(non_empty_features, function(feature_id) { grob_wk_base( meta[feature_id, ], coords[coords$feature_id == feature_id, ], gpar_values_all, rule = rule, default.units = default.units # name and vp are passed to the gTree ) }) grid::gTree(children = do.call(grid::gList, grobs), name = name, vp = vp) } else { stop("Can't use grob_wk_base() on a GEOMETRYCOLLECTION") # nocov } } wkutils/R/filters.R0000644000176200001440000000370314163201260013746 0ustar liggesusers #' Modify well-known geometries #' #' @inheritParams wk::wkb_translate_wkt #' @param srid An integer spatial reference identifier with a user-defined meaning. #' Use `NA` to unset this value. #' @param z A Z value that will be assigned to every coordinate in each feature. #' Use `NA` to unset this value. #' @param trans A 3x3 transformation matrix that will be applied to all coordinates #' in the input. #' #' @return An unclassed well-known vector with the same type #' as the input. #' @export #' #' @examples #' wkt_set_srid("POINT (30 10)", 1234) #' wkt_set_z("POINT (30 10)", 1234) #' wkt_transform( #' "POINT (0 0)", #' # translation +12 +13 #' matrix(c(1, 0, 0, 0, 1, 0, 12, 13, 1), ncol = 3) #' ) #' wkt_set_srid <- function(wkt, srid, precision = 16, trim = TRUE) { recycled <- vctrs::vec_recycle_common(wkt, srid) cpp_wkt_set_srid(recycled[[1]], recycled[[2]], precision, trim) } #' @rdname wkt_set_srid #' @export wkb_set_srid <- function(wkb, srid) { recycled <- vctrs::vec_recycle_common(wkb, srid) cpp_wkb_set_srid(recycled[[1]], recycled[[2]], wk_platform_endian()) } #' @rdname wkt_set_srid #' @export wkt_set_z <- function(wkt, z, precision = 16, trim = TRUE) { recycled <- vctrs::vec_recycle_common(wkt, z) cpp_wkt_set_z(recycled[[1]], recycled[[2]], precision, trim) } #' @rdname wkt_set_srid #' @export wkb_set_z <- function(wkb, z) { recycled <- vctrs::vec_recycle_common(wkb, z) cpp_wkb_set_z(recycled[[1]], recycled[[2]], wk_platform_endian()) } #' @rdname wkt_set_srid #' @export wkt_transform <- function(wkt, trans, precision = 16, trim = TRUE) { cpp_wkt_transform(wkt, as_trans_matrix(trans)[c(1, 2), ], precision, trim) } #' @rdname wkt_set_srid #' @export wkb_transform <- function(wkb, trans) { cpp_wkb_transform(wkb, as_trans_matrix(trans)[c(1, 2), ], endian = wk_platform_endian()) } as_trans_matrix <- function(trans) { trans <- as.matrix(trans) stopifnot(ncol(trans) == 3, nrow(trans) == 3) trans } wkutils/R/draw.R0000644000176200001440000000577414163201260013245 0ustar liggesusers #' Draw well-known geometries #' #' These functions send well-known geometry vectors to a #' graphics device using [graphics::points()], #' [graphics::lines()], and [graphics::polypath()]. These are #' minimal wrappers aimed at developers who need to visualize #' test data: they do not check geometry type and are unlikely #' to work with vectorized graphical parameters in `...`. Use #' the `wk*_plot_new()` functions to initialize a plot using the #' extent of all coordinates in the vector. #' #' @inheritParams wk::wkb_translate_wkt #' @param ... Passed to [graphics::points()], #' [graphics::lines()], or [graphics::polypath()] #' @param rule Passed to [graphics::polypath()] #' @param asp,xlab,ylab,main Passed to [graphics::plot()] to #' initialize a new plot. #' #' @return The input, invisibly #' @export #' #' @examples #' x <- "POLYGON ((0 0, 10 0, 10 10, 0 10, 0 0))" #' #' wkt_plot_new(x) #' wkt_draw_polypath(x, col = "grey90") #' wkt_draw_lines(x, col = "red") #' wkt_draw_points(x) #' wkb_draw_points <- function(wkb, ...) { wkcoords_draw_points(wkb_coords(wkb), ...) invisible(wkb) } #' @rdname wkb_draw_points #' @export wkt_draw_points <- function(wkt, ...) { wkcoords_draw_points(wkt_coords(wkt), ...) invisible(wkt) } #' @rdname wkb_draw_points #' @export wkb_draw_lines <- function(wkb, ...) { wkcoords_draw_lines(wkb_coords(wkb, sep_na = TRUE), ...) invisible(wkb) } #' @rdname wkb_draw_points #' @export wkt_draw_lines <- function(wkt, ...) { wkcoords_draw_lines(wkt_coords(wkt, sep_na = TRUE), ...) invisible(wkt) } #' @rdname wkb_draw_points #' @export wkb_draw_polypath <- function(wkb, ..., rule = "evenodd") { wkcoords_draw_polypath(wkb_coords(wkb, sep_na = TRUE), ..., rule = rule) invisible(wkb) } #' @rdname wkb_draw_points #' @export wkt_draw_polypath <- function(wkt, ..., rule = "evenodd") { wkcoords_draw_polypath(wkt_coords(wkt, sep_na = TRUE), ..., rule = rule) invisible(wkt) } #' @rdname wkb_draw_points #' @export wkb_plot_new <- function(wkb, ..., asp = 1, xlab = "", ylab = "", main = deparse(substitute(wkb))) { wkranges_plot_new(wkb_ranges(wkb, finite = TRUE), ..., asp = asp, xlab = xlab, ylab = ylab, main = main) invisible(wkb) } #' @rdname wkb_draw_points #' @export wkt_plot_new <- function(wkt, ..., asp = 1, xlab = "", ylab = "", main = deparse(substitute(wkt))) { wkranges_plot_new(wkt_ranges(wkt, finite = TRUE), ..., asp = asp, xlab = xlab, ylab = ylab, main = main) invisible(wkt) } wkcoords_draw_points <- function(coords, ...) { graphics::points(coords$x, coords$y, ...) } wkcoords_draw_lines <- function(coords, ...) { graphics::lines(coords$x, coords$y, ...) } wkcoords_draw_polypath <- function(coords, ..., rule = "evenodd") { graphics::polypath(coords$x, coords$y, ..., rule = rule) } wkranges_plot_new <- function(ranges, ..., xlab = "", ylab = "", main = "") { graphics::plot( double(), double(), ..., xlim = c(ranges$xmin, ranges$xmax), ylim = c(ranges$ymin, ranges$ymax), xlab = xlab, ylab = ylab, main = main ) } wkutils/R/RcppExports.R0000644000176200001440000000761114357667367014626 0ustar liggesusers# Generated by using Rcpp::compileAttributes() -> do not edit by hand # Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 cpp_coords_point_translate_wkt <- function(x, y, z, m, precision, trim) { .Call(`_wkutils_cpp_coords_point_translate_wkt`, x, y, z, m, precision, trim) } cpp_coords_point_translate_wkb <- function(x, y, z, m, endian, bufferSize) { .Call(`_wkutils_cpp_coords_point_translate_wkb`, x, y, z, m, endian, bufferSize) } cpp_coords_linestring_translate_wkt <- function(x, y, z, m, featureId, precision, trim) { .Call(`_wkutils_cpp_coords_linestring_translate_wkt`, x, y, z, m, featureId, precision, trim) } cpp_coords_linestring_translate_wkb <- function(x, y, z, m, featureId, endian, bufferSize) { .Call(`_wkutils_cpp_coords_linestring_translate_wkb`, x, y, z, m, featureId, endian, bufferSize) } cpp_coords_polygon_translate_wkt <- function(x, y, z, m, featureId, ringId, precision, trim) { .Call(`_wkutils_cpp_coords_polygon_translate_wkt`, x, y, z, m, featureId, ringId, precision, trim) } cpp_coords_polygon_translate_wkb <- function(x, y, z, m, featureId, ringId, endian, bufferSize) { .Call(`_wkutils_cpp_coords_polygon_translate_wkb`, x, y, z, m, featureId, ringId, endian, bufferSize) } cpp_coords_wkb <- function(wkb, sepNA) { .Call(`_wkutils_cpp_coords_wkb`, wkb, sepNA) } cpp_coords_wkt <- function(wkt, sepNA) { .Call(`_wkutils_cpp_coords_wkt`, wkt, sepNA) } cpp_debug_wkb <- function(wkb) { invisible(.Call(`_wkutils_cpp_debug_wkb`, wkb)) } cpp_debug_wkt <- function(input) { invisible(.Call(`_wkutils_cpp_debug_wkt`, input)) } cpp_debug_wkt_streamer <- function(input) { invisible(.Call(`_wkutils_cpp_debug_wkt_streamer`, input)) } cpp_wkt_set_srid <- function(wkt, srid, precision = 16L, trim = TRUE) { .Call(`_wkutils_cpp_wkt_set_srid`, wkt, srid, precision, trim) } cpp_wkb_set_srid <- function(wkb, srid, endian) { .Call(`_wkutils_cpp_wkb_set_srid`, wkb, srid, endian) } cpp_wkt_set_z <- function(wkt, z, precision = 16L, trim = TRUE) { .Call(`_wkutils_cpp_wkt_set_z`, wkt, z, precision, trim) } cpp_wkb_set_z <- function(wkb, z, endian) { .Call(`_wkutils_cpp_wkb_set_z`, wkb, z, endian) } cpp_wkt_transform <- function(wkt, transform, precision = 16L, trim = TRUE) { .Call(`_wkutils_cpp_wkt_transform`, wkt, transform, precision, trim) } cpp_wkb_transform <- function(wkb, transform, endian) { .Call(`_wkutils_cpp_wkb_transform`, wkb, transform, endian) } cpp_wkt_has_non_finite <- function(wkt) { .Call(`_wkutils_cpp_wkt_has_non_finite`, wkt) } cpp_wkb_has_non_finite <- function(wkb) { .Call(`_wkutils_cpp_wkb_has_non_finite`, wkb) } cpp_wkt_has_missing <- function(wkt) { .Call(`_wkutils_cpp_wkt_has_missing`, wkt) } cpp_wkb_has_missing <- function(wkb) { .Call(`_wkutils_cpp_wkb_has_missing`, wkb) } cpp_meta_wkb <- function(wkb, recursive) { .Call(`_wkutils_cpp_meta_wkb`, wkb, recursive) } cpp_meta_wkt <- function(wkt, recursive) { .Call(`_wkutils_cpp_meta_wkt`, wkt, recursive) } cpp_meta_wkt_streamer <- function(wkt, recursive) { .Call(`_wkutils_cpp_meta_wkt_streamer`, wkt, recursive) } cpp_ranges_wkb <- function(wkb, naRm, onlyFinite) { .Call(`_wkutils_cpp_ranges_wkb`, wkb, naRm, onlyFinite) } cpp_ranges_wkt <- function(wkt, naRm, onlyFinite) { .Call(`_wkutils_cpp_ranges_wkt`, wkt, naRm, onlyFinite) } cpp_feature_ranges_wkb <- function(wkb, naRm, onlyFinite) { .Call(`_wkutils_cpp_feature_ranges_wkb`, wkb, naRm, onlyFinite) } cpp_feature_ranges_wkt <- function(wkt, naRm, onlyFinite) { .Call(`_wkutils_cpp_feature_ranges_wkt`, wkt, naRm, onlyFinite) } cpp_wkt_unnest <- function(wkt, keepEmpty, keepMulti, maxUnnestDepth) { .Call(`_wkutils_cpp_wkt_unnest`, wkt, keepEmpty, keepMulti, maxUnnestDepth) } cpp_wkb_unnest <- function(wkb, keepEmpty, keepMulti, maxUnnestDepth, endian) { .Call(`_wkutils_cpp_wkb_unnest`, wkb, keepEmpty, keepMulti, maxUnnestDepth, endian) } wkutils/R/ranges.R0000644000176200001440000000237214163201260013556 0ustar liggesusers #' Extract ranges information #' #' This is intended to behave the same as [range()], returning the #' minimum and maximum x, y, z, and m coordinate values. #' #' @inheritParams wk::wkb_translate_wkt #' @param na.rm Pass `TRUE` to not consider missing (nan) values #' @param finite Pass `TRUE` to only consider finite #' (non-missing, non-infinite) values. #' @export #' #' @return A data.frame with columns: #' - `xmin`, `ymin`, `zmin`, and `mmin`: Minimum coordinate values #' - `xmax`, `ymax`, `zmax`, and `mmax`: Maximum coordinate values #' #' @examples #' wkt_ranges("POINT (30 10)") #' wkb_ranges <- function(wkb, na.rm = FALSE, finite = FALSE) { new_data_frame(cpp_ranges_wkb(wkb, naRm = na.rm, onlyFinite = finite)) } #' @rdname wkb_ranges #' @export wkt_ranges <- function(wkt, na.rm = FALSE, finite = FALSE) { new_data_frame(cpp_ranges_wkt(wkt, naRm = na.rm, onlyFinite = finite)) } #' @rdname wkb_ranges #' @export wkb_feature_ranges <- function(wkb, na.rm = FALSE, finite = FALSE) { new_data_frame(cpp_feature_ranges_wkb(wkb, naRm = na.rm, onlyFinite = finite)) } #' @rdname wkb_ranges #' @export wkt_feature_ranges <- function(wkt, na.rm = FALSE, finite = FALSE) { new_data_frame(cpp_feature_ranges_wkt(wkt, naRm = na.rm, onlyFinite = finite)) } wkutils/R/plot.R0000644000176200001440000000656714163201260013267 0ustar liggesusers #' Plot well-known geometry vectors #' #' These plot functions are intended to help debug geometry vectors, #' and are not intended to be high-performance. #' #' @param x A [wkt()] or [wkb()] vector. #' @param add Should a new plot be created, or should `x` be added to the #' existing plot? #' @param ... Passed to plotting functions for features: [graphics::points()] #' for point and multipoint geometries, [graphics::lines()] for linestring #' and multilinestring geometries, and [graphics::polypath()] for polygon #' and multipolygon geometries. #' @param bbox The limits of the plot in the form returned by [wkt_ranges()]. #' @param asp,xlab,ylab Passed to [graphics::plot()] #' @param rule The rule to use for filling polygons (see [graphics::polypath()]) #' #' @return `x`, invisibly #' @export #' #' @examples #' wkt_plot("POINT (30 10)") #' wkt_plot <- function(x, ..., asp = 1, bbox = NULL, xlab = "", ylab = "", rule = "evenodd", add = FALSE) { plot_wk( x, wkt_ranges, wkt_meta, wkt_coords, wkt_unnest, ..., asp = asp, bbox = bbox, xlab = xlab, rule = rule, add = add ) } #' @rdname wkt_plot #' @export wkb_plot <- function(x, ..., asp = 1, bbox = NULL, xlab = "", ylab = "", rule = "evenodd", add = FALSE) { plot_wk( x, wkb_ranges, wkb_meta, wkb_coords, wkb_unnest, ..., asp = asp, bbox = bbox, xlab = xlab, rule = rule, add = add ) } plot_wk <- function(x, ranges_fun, meta_fun, coords_fun, unnest_fun, ..., asp = 1, bbox = NULL, xlab = "", ylab = "", rule = "evenodd", add = FALSE) { if (!add) { bbox <- unclass(bbox) bbox <- bbox %||% ranges_fun(x, finite = TRUE) xlim <- c(bbox$xmin, bbox$xmax) ylim <- c(bbox$ymin, bbox$ymax) graphics::plot( numeric(0), numeric(0), xlim = xlim, ylim = ylim, xlab = xlab, ylab = ylab, asp = asp ) } plot_add_wk(x, meta_fun, coords_fun, unnest_fun, ..., rule = rule) } plot_add_wk <- function(x, meta_fun, coords_fun, unnest_fun, ..., rule = "evenodd") { # evaluate dots, wrap scalar types in a list(), and vectorize x <- unclass(x) dots <- list(..., rule = rule) is_scalar <- !vapply(dots, vctrs::vec_is, logical(1)) dots[is_scalar] <- lapply(dots[is_scalar], list) dots_tbl <- vctrs::vec_recycle_common(!!!dots, .size = length(x)) meta <- unclass(meta_fun(x, recursive = FALSE)) # using for() because the user interrupt is respected in RStudio for (i in seq_along(x)) { coords <- coords_fun(x[i], sep_na = TRUE)[c("x", "y")] if (nrow(coords) == 0) { next } dots_item <- lapply(dots_tbl, "[[", i) type_id <- meta$type[i] args <- c(coords, dots_item) if (type_id == 1 || type_id == 4) { args$rule <- NULL do.call(graphics::points, args) } else if (type_id == 2 || type_id == 5) { args$rule <- NULL do.call(graphics::lines, args) } else if (type_id == 3 || type_id == 6) { do.call(graphics::polypath, args) } else if (type_id == 7) { unnested <- unnest_fun(x[i]) do.call( plot_add_wk, c( list(unnested, meta_fun = meta_fun, coords_fun = coords_fun, unnest_fun = unnest_fun), dots_item ) ) } else { stop("Unknown geometry type", call. = FALSE) # nocov } } invisible(x) } wkutils/R/debug.R0000644000176200001440000000141614163201260013363 0ustar liggesusers #' Debug well-known geometry #' #' Prints the raw calls to the `WKBGeometryHandler()`. Useful for writing #' custom C++ handlers and debugging read problems. #' #' @inheritParams wk::wkb_translate_wkt #' #' @return The input, invisibly #' @export #' #' @examples #' wkt_debug("MULTIPOLYGON (((0 0, 10 0, 0 10, 0 0)))") #' wkt_streamer_debug("MULTIPOLYGON (((0 0, 10 0, 0 10, 0 0)))") #' wkb_debug( #' wk::wkt_translate_wkb( #' "MULTIPOLYGON (((0 0, 10 0, 0 10, 0 0)))" #' ) #' ) #' wkb_debug <- function(wkb) { cpp_debug_wkb(wkb) invisible(wkb) } #' @rdname wkb_debug #' @export wkt_debug <- function(wkt) { cpp_debug_wkt(wkt) invisible(wkt) } #' @rdname wkb_debug #' @export wkt_streamer_debug <- function(wkt) { cpp_debug_wkt_streamer(wkt) invisible(wkt) } wkutils/R/wkutils-package.R0000644000176200001440000000044214163201260015366 0ustar liggesusers#' @keywords internal "_PACKAGE" # The following block is used by usethis to automatically manage # roxygen namespace tags. Modify with care! ## usethis namespace: start #' @useDynLib wkutils, .registration = TRUE #' @importFrom Rcpp sourceCpp #' @import wk ## usethis namespace: end NULL wkutils/R/coords-translate.R0000644000176200001440000000703414320423302015561 0ustar liggesusers #' Parse coordinates into well-known formats #' #' These functions provide the reverse function of [wkt_coords()] #' and company: they parse vectors of coordinate values into well-known #' formats. Polygon rings are automatically closed, as #' closed rings are assumed or required by many parsers. #' #' @param x,y,z,m Vectors of coordinate values #' @param feature_id,ring_id Vectors for which a change in #' sequential values indicates a new feature or ring. Use [factor()] #' to convert from a character vector. #' @param buffer_size The buffer size to use when converting to WKB. #' @inheritParams wk::wkb_translate_wkt #' #' @return `*_translate_wkt()` returns a character vector of #' well-known text; `*_translate_wkb()` returns a list #' of raw vectors. #' #' @export #' #' @examples #' coords_point_translate_wkt(1:3, 2:4) #' coords_linestring_translate_wkt(1:5, 2:6, feature_id = c(1, 1, 1, 2, 2)) #' coords_polygon_translate_wkt(c(0, 10, 0), c(0, 0, 10)) #' coords_point_translate_wkt <- function(x, y, z = NA, m = NA, precision = 16, trim = TRUE) { recycled <- vctrs::vec_recycle_common(x, y, z, m) cpp_coords_point_translate_wkt( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], precision = precision, trim = trim ) } #' @rdname coords_point_translate_wkt #' @export coords_point_translate_wkb <- function(x, y, z = NA, m = NA, endian = wk::wk_platform_endian(), buffer_size = 2048) { recycled <- vctrs::vec_recycle_common(x, y, z, m) cpp_coords_point_translate_wkb( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], endian = endian, bufferSize = buffer_size ) } #' @rdname coords_point_translate_wkt #' @export coords_linestring_translate_wkt <- function(x, y, z = NA, m = NA, feature_id = 1L, precision = 16, trim = TRUE) { recycled <- vctrs::vec_recycle_common(x, y, z, m, feature_id) cpp_coords_linestring_translate_wkt( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], recycled[[5]], precision = precision, trim = trim ) } #' @rdname coords_point_translate_wkt #' @export coords_linestring_translate_wkb <- function(x, y, z = NA, m = NA, feature_id = 1L, endian = wk::wk_platform_endian(), buffer_size = 2048) { recycled <- vctrs::vec_recycle_common(x, y, z, m, feature_id) cpp_coords_linestring_translate_wkb( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], recycled[[5]], endian = endian, bufferSize = buffer_size ) } #' @rdname coords_point_translate_wkt #' @export coords_polygon_translate_wkt <- function(x, y, z = NA, m = NA, feature_id = 1L, ring_id = 1L, precision = 16, trim = TRUE) { recycled <- vctrs::vec_recycle_common(x, y, z, m, feature_id, ring_id) cpp_coords_polygon_translate_wkt( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], recycled[[5]], recycled[[6]], precision = precision, trim = trim ) } #' @rdname coords_point_translate_wkt #' @export coords_polygon_translate_wkb <- function(x, y, z = NA, m = NA, feature_id = 1L, ring_id = 1L, endian = wk::wk_platform_endian(), buffer_size = 2048) { recycled <- vctrs::vec_recycle_common(x, y, z, m, feature_id, ring_id) cpp_coords_polygon_translate_wkb( recycled[[1]], recycled[[2]], recycled[[3]], recycled[[4]], recycled[[5]], recycled[[6]], endian = endian, bufferSize = buffer_size ) } wkutils/R/meta.R0000644000176200001440000000375314163201260013231 0ustar liggesusers #' Extract meta information #' #' @inheritParams wk::wkb_translate_wkt #' @param recursive Pass `TRUE` to recurse into multi-geometries #' and collections to extract meta of sub-geometries #' @param type A string version of the geometry type (e.g., #' point, linestring, polygon, multipoint, multilinestring, #' multipolygon, geometrycollection) #' @param type_id An integer version of the geometry type #' #' @return A data.frame with columns: #' - `feature_id`: The index of the top-level feature #' - `nest_id`: The recursion level (if feature is a geometry collection) #' - `part_id`: The part index (if nested within a multi-geometry or collection) #' - `type_id`: The type identifier (see [wk_geometry_type()]) #' - `size`: For points and linestrings the number of points, for polygons #' the number of rings, and for mutlti-geometries and collection types, #' the number of child geometries. #' - `srid`: The spatial reference identifier as an integer #' #' @export #' #' @examples #' wkt_meta("POINT (30 10)") #' wkt_meta("GEOMETRYCOLLECTION (POINT (30 10))", recursive = FALSE) #' wkt_meta("GEOMETRYCOLLECTION (POINT (30 10))", recursive = TRUE) #' wkb_meta <- function(wkb, recursive = FALSE) { new_data_frame(cpp_meta_wkb(wkb, recursive = recursive)) } #' @rdname wkb_meta #' @export wkt_meta <- function(wkt, recursive = FALSE) { new_data_frame(cpp_meta_wkt(wkt, recursive = recursive)) } #' @rdname wkb_meta #' @export wkt_streamer_meta <- function(wkt, recursive = FALSE) { new_data_frame(cpp_meta_wkt_streamer(wkt, recursive = recursive)) } #' @rdname wkb_meta #' @export wk_geometry_type <- function(type_id) { c( "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection" )[as.integer(type_id)] } #' @rdname wkb_meta #' @export wk_geometry_type_id <- function(type) { match( type, c( "point", "linestring", "polygon", "multipoint", "multilinestring", "multipolygon", "geometrycollection" ) ) } wkutils/R/finite.R0000644000176200001440000000166214163201260013556 0ustar liggesusers #' Test well-known geometries for missing and non-finite coordinates #' #' Note that EMTPY geometries are considered finite and non-missing. #' Use the `size` column of [wkt_meta()] to test for empty geometries. #' #' @inheritParams wk::wkb_translate_wkt #' #' @return A logical vector with the same length as the input. #' @export #' #' @examples #' wkt_has_missing("POINT (0 1)") #' wkt_has_missing("POINT (nan nan)") #' wkt_has_missing("POINT (inf inf)") #' #' wkt_is_finite("POINT (0 1)") #' wkt_is_finite("POINT (nan nan)") #' wkt_is_finite("POINT (inf inf)") #' wkt_has_missing <- function(wkt) { cpp_wkt_has_missing(wkt) } #' @rdname wkt_has_missing #' @export wkb_has_missing <- function(wkb) { cpp_wkb_has_missing(wkb) } #' @rdname wkt_has_missing #' @export wkt_is_finite <- function(wkt) { !cpp_wkt_has_non_finite(wkt) } #' @rdname wkt_has_missing #' @export wkb_is_finite <- function(wkb) { !cpp_wkb_has_non_finite(wkb) } wkutils/NEWS.md0000644000176200001440000000100314361406147013052 0ustar liggesusers# wkutils 0.1.3 * Added a `#include ` to fix CRAN check error on gcc13 (#6). # wkutils 0.1.2 * Vendored wk headers that are no longer being shipped with wk in the development version. # wkutils 0.1.1 * Removed support for `wk::wksxp()` for future compatibility with wk. * Fixed error caught by CRAN ASAN/UBSAN checks. * `wkb_plot()` and `wkt_plot()` can now accept unclassed input for improved consistency. # wkutils 0.1.0 * Moved utility functions from the 'wk' package to create this package. wkutils/MD50000644000176200001440000001031114361730142012261 0ustar liggesusers6af435dcdade184c3eec49fcc017bc10 *DESCRIPTION 3af0d5f6726a14ed40722905f3202a79 *LICENSE 77b12c0e6a726839ea3be98774feeb0e *NAMESPACE 8d66cd416b191db44ac9a2719d4af0d3 *NEWS.md 9e6b26eae75568a77a214674fb173ea7 *R/RcppExports.R 9b68640e9f7cb7f914f37ecd7617bc87 *R/coords-translate.R 4e3ddc2b5b4a93f1c64dc1af6c1c4e25 *R/coords.R e9ccaf1ce2b1766a32797d1c4f5fc8af *R/debug.R 7955e5795b14ba12bccb45da95f22edb *R/draw.R 84eeb955013fc4c1476cb91f6d0a7389 *R/filters.R a2d343dacc83eda7eac0580e7d9fb303 *R/finite.R a03a8d203015983c2fe93b86603a1c73 *R/grob.R ac9857c4955589fbc8634bda894b11e6 *R/meta.R 4520275e628e5ce20c19e9e2be471db0 *R/plot.R 768005c337aaa29ce1d1a8b749579b30 *R/ranges.R 4163cef7f22f8806ad2526b17e1ae9e7 *R/unnest.R 75f89fdecf90e0412e14ad8a597f95a7 *R/utils.R a6f04b54508534a3b3b40530f9d63fce *R/wkutils-package.R fd2c8453e3cda5436a2a95bea29e6b21 *README.md 56cbc6a0e56637ead7633384356f765b *man/coords_point_translate_wkt.Rd 2d80fbf245f8d65e398cb17dcefaeee0 *man/figures/README-wk-draw-1.png 1682937efd2a050b367b84fc20f1e8c4 *man/figures/README-wkt-plot-1.png 247ae286d11f103709609471c001f4ba *man/wkb_coords.Rd e262140c0c93e424008aba6b8d4edd5a *man/wkb_debug.Rd c4623233285a8fb6308da4052b77da75 *man/wkb_draw_points.Rd da271793492c94adb9f18635852148ca *man/wkb_meta.Rd 5b2f881d3236970b2de51373ee62d706 *man/wkb_ranges.Rd 121b4a91dccd4f5261dc1f97eae13445 *man/wkt_grob.Rd 7dfdbd10c0aa4cb37f89367e4f0f7d2c *man/wkt_has_missing.Rd f7adf1cb34b83bea1b3944b7b43e0041 *man/wkt_plot.Rd 73c9f7ac057480f200a99cdef46de585 *man/wkt_set_srid.Rd bb69417ba7e9b59a97c6afd1eaccb527 *man/wkt_unnest.Rd f6b35d0a4b84ce0acf4b1627349e94b7 *man/wkutils-package.Rd de73108e16362ecaad1248f92b4ae917 *src/Makevars 393ac411e2101da848b7c3d74fc2b494 *src/RcppExports.cpp b0b9afa93564fa41e369f188099a698b *src/coords-translate.cpp adf36642401522d0b6052cd0ced98836 *src/coords.cpp 0e4b6fa3cdb3a28aef7f706228bc4205 *src/debug.cpp 4da420b79d77659a86f1565c1e60fdb8 *src/filters.cpp a584f9f8866f801bc5fc05dee29bf386 *src/finite.cpp ff98d69a92eae95fd5e7826434affa37 *src/meta.cpp a9dba5b383e216ccfa29f9b15915b6ca *src/ranges.cpp e9e1d5ba448e88b03a700c0951c94050 *src/unnest.cpp a89e851498bc705106625eb60ec60880 *src/wk/coord.hpp 53c88e001fb0d554a35f2edca46d8bde *src/wk/error-formatter.hpp 949907ddc4641f873e46809e5e0850d2 *src/wk/fields.hpp 637278cf7d166a0ebd57cb1d49a50848 *src/wk/filter.hpp 7f953862bd3f302447d0f12593310a3d *src/wk/geometry-debug-handler.hpp e5a00323d702c9991a9b709a9038f0d3 *src/wk/geometry-formatter.hpp 53c58251e66114faeced948cc896a0f4 *src/wk/geometry-handler.hpp fdad029efcd896f59c0ec2d717a6e00a *src/wk/geometry-meta.hpp b7d7fc09f37716491ee7972b11653e01 *src/wk/geometry.hpp c7830a7ff278c878ef831b4988baec5d *src/wk/io-bytes.hpp 9678f9d92b6f00750e6433c085b6f73a *src/wk/io-string.hpp d9eb0147aa913a63756225d6470d8334 *src/wk/io.hpp bf5333984941bfcbdcbad1fc72518b44 *src/wk/parse-exception.hpp f6603a5b82b0efe0e6a784ef98adb9a4 *src/wk/rcpp-coord-reader.hpp 7724ff6078e2b3a621c1ce69e6f81a92 *src/wk/rcpp-io.hpp c23792454a0afd53dc9b8bd55f885e5a *src/wk/rcpp-translate.hpp 1739f9ccc458c9c74e42a37e71937da3 *src/wk/rct.hpp 676b944a4a46ef4a4ed2bef84e58cc8e *src/wk/reader.hpp b2b159e10dd2173b013430920f3e5e1a *src/wk/wkb-reader.hpp 43060bf580c3886cb42083bb5330f8ac *src/wk/wkb-writer.hpp 691996f4c41a21769711097f47e14fa6 *src/wk/wkt-reader.hpp f579c21ee106c62b8f2bc03f6e37221d *src/wk/wkt-streamer.hpp e52814f7637bf098881a82cc88c2676b *src/wk/wkt-writer.hpp ac0f8ace733cbad1f90c01060287fa3e *src/wk/writer.hpp 25ae3d77517713a9ab3adede813a51ce *src/wk/xyzm.hpp 141df2b846189579dace1feeebbfe7f4 *tests/testthat.R 8f42976e3a7aad667303b67fc124d14c *tests/testthat/test-coords-translate.R f84d25ac73818ae0a26906bae946c10b *tests/testthat/test-coords.R bd7837250996fca1b1ae37908a3547ab *tests/testthat/test-debug.R ebc3f77b161f31f52e05a16ff8de7caf *tests/testthat/test-draw.R cd3eb94b12563aad400342e2c4e6d719 *tests/testthat/test-filters.R 3bdcc7047a3b0ddde358b69c5bc9a8b3 *tests/testthat/test-finite.R 76a275bd8e46090808d051e962ca9a20 *tests/testthat/test-grob.R d99a345473580019c5f8012d506e8dcf *tests/testthat/test-meta.R a47b578d83b447654b16cd103ddef9ce *tests/testthat/test-plot.R 38e9776dab091e512de0d4d7077e3d66 *tests/testthat/test-ranges.R 99fb351530970bdd5d853c1071d15eb8 *tests/testthat/test-unnest.R