diff --git a/DESCRIPTION b/DESCRIPTION index 258e726..54b04a9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -8,7 +8,7 @@ Authors@R: person("Benedikt", "Graeler", role = "ctb"), person("Nikolai", "Gorte", role = "ctb")) Depends: R (>= 3.0.0) -Imports: stats, utils, graphics, methods, lattice, sp (>= 1.1-0), spacetime (>= 1.0-0) +Imports: stats, utils, graphics, methods, lattice, sp (>= 1.1-0), spacetime (>= 1.0-0), spatstat Suggests: rgdal, rgeos, rgl, OpenStreetMap, RCurl, rjson, adehabitatLT, xts, knitr LazyData: no Description: Classes and methods for trajectory data, with support for nesting individual Track objects in track sets (Tracks) and track sets for different entities in collections of Tracks (TracksCollection). Methods include selection, generalization, aggregation, intersection, simulation, and plotting. diff --git a/NAMESPACE b/NAMESPACE index fdf1ea7..8de8eca 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -2,11 +2,15 @@ import(methods) import(sp) import(spacetime) import(lattice) -importFrom(stats, approx, as.formula, na.omit, quantile, rnorm, arima.sim) +importFrom(stats, approx, as.formula, na.omit, quantile, rnorm, arima.sim, + rpois, runif) # importFrom(stats, aggregate, na.omit, time, start, end) importFrom(utils, head, tail, stack, unstack) -importFrom(graphics, arrows, box, lines, points, segments) +importFrom(graphics, arrows, box, lines, points, segments, legend, polygon) importFrom(grDevices, rainbow) +importFrom(spatstat, marks, psp, midpoints.psp, lengths.psp, + 'marks<-', owin, as.ppp, pairdist, density.psp, density.ppp, + Kinhom, pcfinhom, idw) exportClasses( Track, diff --git a/R/Trackstat.R b/R/Trackstat.R index 7f1010d..5222e77 100644 --- a/R/Trackstat.R +++ b/R/Trackstat.R @@ -4,12 +4,12 @@ # to length of each column in X. as.Track <- function(X,covariate){ stopifnot(nrow(X)>0) - colnames(X) <- c("xcoor","ycoor","time") + # colnames(X) <- c("xcoor","ycoor","time") if(!is.data.frame(X)) X <- as.data.frame(X) sp <- cbind(x=X$xcoor,y=X$ycoor) sp <- SpatialPoints(sp) t <- as.POSIXct(paste(X$date,X$time)) - if(missing(covariate)) covariate <- data.frame(d=rep(NA,length(X$xcoor))) + if(missing(covariate)) covariate <- data.frame(d=rep(1,length(X$xcoor))) return(Track(STIDF(sp,time = t,data =covariate))) } @@ -89,49 +89,19 @@ avedistTrack <- function(X,timestamp){ # calculate a sequance of time to interpolate tracks within this sequance timeseq <- tsqTracks(X,timestamp = timestamp) - # reconstruct tracks in sequance timeseq - Z <- lapply(X,reTrack,tsq = timeseq,at="dfrm") + Y <- as.Track.ppp(X,timestamp) - wincor <- lapply(X=1:length(Z),FUN = function(i){ - return(list(min(Z[[i]]$x),max(Z[[i]]$x),min(Z[[i]]$y),max(Z[[i]]$y))) + avedist <- lapply(X=1:length(Y), function(i){ + pd <- pairdist(Y[[i]]) + mean(pd[pd>0]) }) - wincor <- matrix(unlist(wincor),nrow = 4) - w <- owin(c(min(wincor[1,]),max(wincor[2,])),c(min(wincor[3,]),max(wincor[4,]))) - # create a list and convert tracks in each element of timeseq to an object of calss ppp - p <- list() - for (j in 1:length(timeseq)) { - - l <- lapply(X=1:length(Z), function(i){ - Z[[i]][which(Z[[i]]$time==timeseq[j]),-3] - }) - - if(length(unlist(l))>0){ - x <- unlist(lapply(X=1:length(l),function(k){ - l[[k]]$x - })) - y <- unlist(lapply(X=1:length(l),function(h){ - l[[h]]$y - })) - p[[j]] <- as.ppp(data.frame(x,y),W=w) - } - - } - - p1 <- p[!sapply(p, is.null)] - avedist <- unlist( - lapply(X=1:length(p1), function(i){ - pd <- pairdist(p1[[i]]) - pd <- pd[pd>0] - return(mean(pd)) - }) - ) - avedist <- data.frame(timeseq[!sapply(p, is.null)],avedist) + avedist <- data.frame(timeseq[-1],unlist(avedist)) + colnames(avedist) <- c("timeseq","avedist") class(avedist) <- c("distrack") - attr(avedist,"ppp") <- p + attr(avedist,"ppp") <- Y return(avedist) } - print.distrack <- function(x){ print(as.vector(x$avedist)) } @@ -147,6 +117,7 @@ rmdupTrack <- function(X){ return(as.Track(X)) } + as.Track.ppp <- function(X,timestamp){ stopifnot(length(X)>1 & is.list(X)) @@ -157,43 +128,20 @@ as.Track.ppp <- function(X,timestamp){ # reconstruct tracks in sequance timeseq Z <- lapply(X,reTrack,tsq = timeseq,at="dfrm") - - wincor <- lapply(X=1:length(Z),FUN = function(i){ - return(list(min(Z[[i]]$x),max(Z[[i]]$x),min(Z[[i]]$y),max(Z[[i]]$y))) + id <- rep(1:length(Z),sapply(Z, nrow)) + Z <- do.call("rbind",Z) + Z <- cbind(Z,id) + allZ <- split(Z,Z[,3]) + w <- owin(c(min(Z$xcoor)-0.001,max(Z$xcoor)+0.001),c(min(Z$ycoor)-0.001,max(Z$ycoor)+0.001)) + + Tppp <- lapply(X=1:length(allZ), function(i){ + p <- as.ppp(allZ[[i]][,-c(3,4)],W=w) + marks(p) <- allZ[[i]][,4] + return(p) }) - wincor <- matrix(unlist(wincor),nrow = 4) - w <- owin(c(min(wincor[1,]),max(wincor[2,])),c(min(wincor[3,]),max(wincor[4,]))) - - # create a list and convert tracks in each element of timeseq to an object of calss ppp - p <- list() - - for (j in 1:length(timeseq)) { - - l <- lapply(X=1:length(Z), function(i){ - w <- which(Z[[i]]$time==timeseq[j]) - if (length(w)>0) return(cbind(Z[[i]][w,-3],id=i)) - return(Z[[i]][w,-3]) - }) - - if(length(unlist(l))>0){ - x <- unlist(lapply(X=1:length(l),function(k){ - l[[k]]$x - })) - y <- unlist(lapply(X=1:length(l),function(h){ - l[[h]]$y - })) - m <- unlist(lapply(X=1:length(l),function(h){ - l[[h]]$id - })) - p[[j]] <- as.ppp(data.frame(x,y),W=w) - marks(p[[j]]) <- m - } - - } - return(p) + return(Tppp) } - density.Track <- function(X,timestamp,...){ stopifnot(length(X)>1 & is.list(X)) @@ -304,15 +252,17 @@ chimaps <- function(X,timestamp,rank,...){ } Kinhom.Track <- function(X,timestamp, - correction=c("border", "bord.modif", "isotropic", "translate"),q,...){ + correction=c("border", "bord.modif", "isotropic", "translate"),q, + sigma=c("bw.diggle","bw.ppl"," bw.scott"),...){ stopifnot(length(X)>1 & is.list(X)) if (missing(timestamp)) stop("set timestamp") cor <- match.arg(correction,correction) - - ZZ <- density.Track(X,timestamp) + bw <- match.arg(sigma,sigma) + bw <- match.fun(bw) + ZZ <- density.Track(X,timestamp,bw) Z <- attr(ZZ,"Tracksim") Y <- attr(ZZ,"ppps") @@ -410,3 +360,57 @@ plot.gTrack <- function(x,type="l",col= "grey70",...){ points(x$r,x$theo,type=type,col=2) points(x$r,x$aveg,type=type) } + + +rTrack <- function (n = 100, origin = c(0, 0), start = as.POSIXct("1970-01-01"), + ar = 0.8, step = 60, sd0 = 1,bbox=bbox, transform=FALSE,nrandom=FALSE, ...){ + + if(nrandom) repeat{n <- rpois(1,n);if(!n==0) break()} + if (missing(bbox) & transform) { + xo <- runif(1) + yo <- runif(1) + origin <- c(xo,yo) + } + if (!missing(bbox) & transform) { + xo <- runif(1,bbox[1,1],bbox[1,2]) + yo <- runif(1,bbox[2,1],bbox[2,2]) + origin <- c(xo,yo) + } + if (length(ar) == 1 && ar == 0) + xy = cbind(cumsum(rnorm(n, sd = sd0)) + origin[1], cumsum(rnorm(n, + sd = sd0)) + origin[2]) + else {xy = cbind(origin[1] + cumsum(as.vector(arima.sim(list(ar = ar), + n, sd = sd0, ...))), + origin[2] + cumsum(as.vector(arima.sim(list(ar = ar), + + n, sd = sd0, ...))))} + if(transform) { + if(missing(bbox)) bbox <- matrix(c(0,1,0,1),nrow = 2,byrow = T); colnames(bbox) <- c("min","max");rownames(bbox) <- c("x","y") + + xr <- max(xy[,1])-min(xy[,1]) + yr <- max(xy[,2])-min(xy[,2]) + + xt <- (xy[,1]-min(xy[,1]))/xr + yt <- (xy[,2]-min(xy[,2]))/yr + + xy <- cbind(xt,yt) + xy <- cbind(x=xy[,1]*bbox[1,2],y=xy[,2]*bbox[2,2]) + } + + T = start + 0:(n - 1) * step + sti = STI(SpatialPoints(xy), T) + out <- Track(sti) + if (transform) out@sp@bbox <- bbox + return(out) +} + + +rTracks <- function (m = 20, start = as.POSIXct("1970-01-01"), delta = 7200, + sd1 = 0, origin = c(0, 0), ...) + Tracks(lapply(0:(m - 1) * delta, function(x) rTrack(start = start + + x, origin = origin + rnorm(2, sd = sd1), ...))) + +rTracksCollection <- function (p = 10, sd2 = 0, ...) + TracksCollection(lapply(1:p, function(x) rTracks(origin = rnorm(2, + sd = sd2), ...))) + diff --git a/man/Track-class.Rd b/man/Track-class.Rd index c04e6a8..216ef11 100644 --- a/man/Track-class.Rd +++ b/man/Track-class.Rd @@ -13,6 +13,9 @@ \alias{[,Track-method} \alias{[,Tracks-method} \alias{[,TracksCollection-method} +\alias{[,Track,ANY,ANY,ANY-method} +\alias{[,Tracks,ANY,ANY,ANY-method} +\alias{[,TracksCollection,ANY,ANY,ANY-method} \alias{[[,Track,ANY,missing-method} \alias{[[,Tracks,ANY,missing-method} \alias{[[,TracksCollection,ANY,missing-method} diff --git a/man/rtrack.Rd b/man/rtrack.Rd index db46529..bfb25ad 100644 --- a/man/rtrack.Rd +++ b/man/rtrack.Rd @@ -8,7 +8,7 @@ \description{Generate random \code{Track}, \code{Tracks} or \code{TracksCollection} objects} \usage{ rTrack(n = 100, origin = c(0,0), start = as.POSIXct("1970-01-01"), ar = .8, - step = 60, sd0 = 1, ...) + step = 60, sd0 = 1, bbox = bbox, transform = FALSE, nrandom = FALSE, ...) rTracks(m = 20, start = as.POSIXct("1970-01-01"), delta = 7200, sd1 = 0, origin = c(0,0), ...) rTracksCollection(p = 10, sd2 = 0, ...) @@ -22,6 +22,9 @@ rTracksCollection(p = 10, sd2 = 0, ...) \item{sd0}{standard deviation of the random steps in a Track} \item{sd1}{standard deviation of the consecutive Track origin values (using rnorm)} \item{sd2}{standard deviation of the consecutive Tracks origin values (using rnorm)} +\item{bbox}{bbox object FIXME:fill in} +\item{transform}{logical; FIXME:fill in } +\item{nrandom}{logical; if \code{TRUE}, draw \code{n} from \code{rpois(n)}} \item{...}{rTrack: arguments passed on to \link[stats]{arima.sim}, rTracks: arguments passed on to rTrack; rTracksCollection: arguments passed on to rTracks} \item{m}{ number of Track objects to simulate} diff --git a/vignettes/trsim.html b/vignettes/trsim.html new file mode 100644 index 0000000..8cde039 --- /dev/null +++ b/vignettes/trsim.html @@ -0,0 +1,389 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + +
+

Interpolating, smoothing, and simulating trajectories

+
+

create some data

+
t0 = as.POSIXct(as.Date("2013-09-30",tz="CET"))
+# person A, track 1:
+x = c(7,6,5,5,4,3,3)
+y = c(7,7,6,5,5,6,7)
+n = length(x)
+set.seed(131)
+t = t0 + cumsum(runif(n) * 60)
+library(sp)
+require(rgdal)
+
## Loading required package: rgdal
+
## rgdal: version: 1.2-15, (SVN revision 691)
+##  Geospatial Data Abstraction Library extensions to R successfully loaded
+##  Loaded GDAL runtime: GDAL 2.1.2, released 2016/10/24
+##  Path to GDAL shared files: /usr/share/gdal/2.1
+##  GDAL binary built with GEOS: TRUE 
+##  Loaded PROJ.4 runtime: Rel. 4.9.3, 15 August 2016, [PJ_VERSION: 493]
+##  Path to PROJ.4 shared files: (autodetected)
+##  Linking to sp version: 1.2-5
+
crs = CRS("+proj=longlat +ellps=WGS84") # longlat
+
+library(spacetime)
+stidf = STIDF(SpatialPoints(cbind(x,y),crs), t, data.frame(co2 = rnorm(n)))
+
+library(trajectories)
+A1 = Track(stidf)
+# person A, track 2:
+x = c(7,6,6,7,7)
+y = c(6,5,4,4,3)
+n = length(x)
+t = max(t) + cumsum(runif(n) * 60)
+stidf = STIDF(SpatialPoints(cbind(x,y),crs), t, data.frame(co2 = rnorm(n)))
+
+A2 = Track(stidf)
+# Tracks for person A:
+A = Tracks(list(A1=A1,A2=A2))
+# person B, track 1:
+x = c(2,2,1,1,2,3)
+y = c(5,4,3,2,2,3)
+n = length(x)
+t = max(t) + cumsum(runif(n) * 60)
+stidf = STIDF(SpatialPoints(cbind(x,y),crs), t, data.frame(co2 = rnorm(n)))
+B1 = Track(stidf)
+# person B, track 2:
+x = c(3,3,4,3,3,4)
+y = c(5,4,3,2,1,1)
+n = length(x)
+t = max(t) + cumsum(runif(n) * 60)
+stidf = STIDF(SpatialPoints(cbind(x,y),crs), t, data.frame(co2 = rnorm(n)))
+B2 = Track(stidf)
+# Tracks for person A:
+B = Tracks(list(B1=B1,B2=B2))
+Tr = TracksCollection(list(A=A,B=B))
+stplot(Tr, scales = list(draw=TRUE))
+

+
stplot(Tr, attr = "direction", arrows=TRUE, lwd = 3, by = "direction")
+

+
stplot(Tr, attr = "direction", arrows=TRUE, lwd = 3, by = "IDs")
+

+
plot(Tr, col=2, axes=TRUE)
+

+
dim(Tr)
+
##        IDs     tracks geometries 
+##          2          4         24
+
dim(Tr[2])
+
##     tracks geometries 
+##          2         12
+
dim(Tr[2][1])
+
## geometries 
+##          6
+
u = stack(Tr) # four IDs
+dim(u)
+
##        IDs     tracks geometries 
+##          4          4         24
+
dim(unstack(u, c(1,1,2,2))) # regroups to original
+
##        IDs     tracks geometries 
+##          2          4         24
+
dim(unstack(u, c(1,1,2,3))) # regroups to three IDs
+
##        IDs     tracks geometries 
+##          3          4         24
+
dim(unstack(u, c(1,2,2,1))) # regroups differently
+
##        IDs     tracks geometries 
+##          2          4         24
+
as(Tr, "data.frame")[1:10,] # tracks separated by NA rows
+
##         x  y sp.ID                time             endTime timeIndex
+## A.A1.1  7  7     1 2013-09-30 02:00:12 2013-09-30 02:00:12         1
+## A.A1.2  6  7     2 2013-09-30 02:00:19 2013-09-30 02:00:19         2
+## A.A1.3  5  6     3 2013-09-30 02:00:37 2013-09-30 02:00:37         3
+## A.A1.4  5  5     4 2013-09-30 02:01:00 2013-09-30 02:01:00         4
+## A.A1.5  4  5     5 2013-09-30 02:01:50 2013-09-30 02:01:50         5
+## A.A1.6  3  6     6 2013-09-30 02:02:22 2013-09-30 02:02:22         6
+## A.A1.7  3  7     7 2013-09-30 02:02:53 2013-09-30 02:02:53         7
+## A.A1.8 NA NA    NA                <NA>                <NA>        NA
+## A.A2.1  7  6     1 2013-09-30 02:03:31 2013-09-30 02:03:31         1
+## A.A2.2  6  5     2 2013-09-30 02:04:09 2013-09-30 02:04:09         2
+##                co2 Track IDs
+## A.A1.1 -0.71322105    A1   A
+## A.A1.2  1.37185185    A1   A
+## A.A1.3 -0.39982855    A1   A
+## A.A1.4 -0.47880016    A1   A
+## A.A1.5 -0.54870456    A1   A
+## A.A1.6  0.48757652    A1   A
+## A.A1.7 -0.06981164    A1   A
+## A.A1.8          NA    A1   A
+## A.A2.1  1.40377616    A2   A
+## A.A2.2  0.79969808    A2   A
+
as(Tr, "segments")[1:10,]   # track segments as records
+
##        x0 y0 x1 y1                time        co2 distance duration
+## A.A1.1  7  7  6  7 2013-09-30 02:00:12 -0.7132211 110495.2  7.49653
+## A.A1.2  6  7  5  6 2013-09-30 02:00:19  1.3718518 156407.3 17.59639
+## A.A1.3  5  6  5  5 2013-09-30 02:00:37 -0.3998285 110583.3 22.54678
+## A.A1.4  5  5  4  5 2013-09-30 02:01:00 -0.4788002 110898.7 50.78081
+## A.A1.5  4  5  3  6 2013-09-30 02:01:50 -0.5487046 156547.2 31.75229
+## A.A1.6  3  6  3  7 2013-09-30 02:02:22  0.4875765 110587.4 31.11752
+## A.A2.1  7  6  6  5 2013-09-30 02:03:31  1.4037762 156547.2 37.52483
+## A.A2.2  6  5  6  4 2013-09-30 02:04:09  0.7996981 110579.9 24.03699
+## A.A2.3  6  4  7  4 2013-09-30 02:04:33 -0.7110605 111050.1 34.96193
+## A.A2.4  7  4  7  3 2013-09-30 02:05:08 -1.5005960 110577.2 19.64212
+##            speed direction Track IDs
+## A.A1.1 14739.515 270.06094    A1   A
+## A.A1.2  8888.604 224.87295    A1   A
+## A.A1.3  4904.618 180.00000    A1   A
+## A.A1.4  2183.870 270.04358    A1   A
+## A.A1.5  4930.266 315.17903    A1   A
+## A.A1.6  3553.863   0.00000    A1   A
+## A.A2.1  4171.830 224.91682    A2   A
+## A.A2.2  4600.407 180.00000    A2   A
+## A.A2.3  3176.316  89.96512    A2   A
+## A.A2.4  5629.598 180.00000    A2   A
+
Tr[["distance"]] = Tr[["distance"]] * 1000
+Tr$distance = Tr$distance / 1000
+Tr$distance
+
##    A.A11    A.A12    A.A13    A.A14    A.A15    A.A16    A.A21    A.A22 
+## 110495.2 156407.3 110583.3 110898.7 156547.2 110587.4 156547.2 110579.9 
+##    A.A23    A.A24    B.B11    B.B12    B.B13    B.B14    B.B15    B.B21 
+## 111050.1 110577.2 110579.9 156757.4 110575.2 111252.1 156827.6 110579.9 
+##    B.B22    B.B23    B.B24    B.B25 
+## 156757.4 156827.6 110573.8 111302.6
+
# work with custum TrackStats function:
+MyStats = function(track) {
+    df = apply(coordinates(track@sp), 2, diff) # requires sp
+    data.frame(distance = apply(df, 1, function(x) sqrt(sum(x^2))))
+}
+crs = CRS(as.character(NA))
+stidf = STIDF(SpatialPoints(cbind(x,y),crs), t, data.frame(co2 = rnorm(n)))
+B2 = Track(stidf) # no longer longlat;
+B3 = Track(stidf, fn = MyStats)
+all.equal(B3$distance, B2$distance)
+
## [1] TRUE
+
+
+

Interpolating trajectories

+
opar = par()
+par(mfrow = c(1, 2))
+plot(B2, ylim = c(.5, 6))
+plot(B2, pch = 16, add = TRUE)
+title("irregular time steps")
+i = index(B2)
+B3 = approxTrack(B2, seq(min(i), max(i), length.out = 50))
+plot(B3, col = 'red', type = 'p', add = TRUE)
+B4 = approxTrack(B2, seq(min(i), max(i), length.out = 50), FUN = spline)
+plot(B4, col = 'blue', type = 'b', add = TRUE)
+# regular time steps:
+t = max(t) + (1:n) * 60 # regular
+B2 = Track(STIDF(SpatialPoints(cbind(x,y),crs), t, data.frame(co2 = rnorm(n))))
+plot(B2, ylim = c(.5, 6))
+plot(B2, pch = 16, add = TRUE)
+title("constant time steps")
+i = index(B2)
+B3 = approxTrack(B2)
+plot(B3, type = 'p', col = 'red', add = TRUE)
+B4 = approxTrack(B2, FUN = spline)
+plot(B4, type = 'p', col = 'blue', add = TRUE)
+

+
par(opar)
+
## Warning in par(opar): graphical parameter "cin" cannot be set
+
## Warning in par(opar): graphical parameter "cra" cannot be set
+
## Warning in par(opar): graphical parameter "csi" cannot be set
+
## Warning in par(opar): graphical parameter "cxy" cannot be set
+
## Warning in par(opar): graphical parameter "din" cannot be set
+
## Warning in par(opar): graphical parameter "page" cannot be set
+
+
+

Smoothing trajectories

+
smth = function(x,y,xout,...) predict(smooth.spline(as.numeric(x), y), as.numeric(xout))
+data(storms)
+plot(storms, type = 'p')
+
## Warning in axis(side, at = at, labels = labels, ...): graphical parameter
+## "type" is obsolete
+
+## Warning in axis(side, at = at, labels = labels, ...): graphical parameter
+## "type" is obsolete
+
## Warning in title(...): graphical parameter "type" is obsolete
+
storms.smooth = approxTracksCollection(storms, FUN = smth, n = 200)
+
## Warning in validityMethod(object): tracks with overlapping time intervals:
+## 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18
+
## Warning in validityMethod(object): tracks with overlapping time intervals:
+## 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
+
## Warning in validityMethod(object): tracks with overlapping time intervals:
+## 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
+
## Warning in validityMethod(object): tracks with overlapping time intervals:
+## 1, 2, 3, 4, 5, 6, 7, 8, 9, 10
+
plot(storms.smooth, add = TRUE, col = 'red')
+

+
+
+

Simulating random trajectories

+
x = rTrack()
+dim(x)
+
## geometries 
+##        100
+
plot(x)
+

+
x = rTracks(sd1 = 120)
+dim(x)
+
##     tracks geometries 
+##         20       2000
+
plot(as(x, "SpatialLines"), col = 1:dim(x)[1], axes=TRUE)
+

+
x = rTracksCollection() # star
+dim(x)
+
##        IDs     tracks geometries 
+##         10        200      20000
+
plot(x)
+

+
x = rTracksCollection(sd2 = 200)
+plot(x, col=1:dim(x)[1])
+

+
+
+ + + + +
+ + + + + + + +