# R/similarityMeasures.R In SimilarityMeasures: Trajectory Similarity Measures

#### Documented in AveTranslateDistanceCheckDistanceSqDotDTWEditDistFrechetFrechetCheckLCSSLCSSCalcLCSSRatioLCSSRatioCalcLCSSTranslationSimLoopSinglePointCalcStartEndTranslateTrajCheckTranslationSubset

```# Similarity Measures Version 1.4, 2015-02-06
# Created by Kevin Toohey, 2014, License: GPL-3

# This file contains functions to run and assist four different similarity
# measures. The similarity measures included are: longest common
# subsequence (LCSS), Frechet distance, edit distance and dynamic time
# warping (DTW). Each of these similarity measures can be calculated from
# two n-dimensional trajectories, both in matrix form.

LCSSRatio <- function(traj1, traj2, pointSpacing=-1, pointDistance=20,
errorMarg=2, returnTrans=FALSE) {
# Function to calculate the ratio of the longest common sub sequence to
# the shortest trajectory.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#   errorMarg: A floating point error margin used to scale the accuracy
#              and speed of the calculation.
#   returnTrans: A boolean value to allow the best translation found to
#                be returned as well as the LCSS value if set to true.
#
# Returns:
#   A floating point value is returned. This represents the maximum LCSS
#   ratio obtained using the variables provided. If returnTrans is set to
#   TRUE, then the LCSS ratio and the translations are returned as a
#   vector. The first value of this vector is the LCSS ratio and the
#   translations follow directly afterwards. If a problem occurs, then a
#   string containing information about the problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating the number of points in each trajectory and the dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a trajectory has no points then the ratio is 0.
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
return(0.0)
}
# Calculating the ratio based on the shortest trajectory.
length <- min(length1, length2)
return(LCSS(traj1, traj2, pointSpacing, pointDistance,
errorMarg, returnTrans) * 1.0 / length)
}

LCSS <- function(traj1, traj2, pointSpacing=-1, pointDistance=20,
errorMarg=2, returnTrans=FALSE) {
# Function to calculate the longest common subsequence for two
# given trajectories.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#   errorMarg: A floating point error margin used to scale the accuracy
#              and speed of the calculation.
#   returnTrans: A boolean value to allow the best translation found to
#                be returned as well as the LCSS value if set to true.
#
# Returns:
#   An integer value is returned. This represents the maximum LCSS
#   value obtained using the variables provided. If returnTrans is set
#   to TRUE, then the LCSS value and the translations are returned as a
#   vector. The first value of this vector is the LCSS value and the
#   translations follow directly afterwards. If a problem occurs, then a
#   string containing information about the problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating the number of points in each trajectory and the dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a trajectory has no points then there are 0 similar points.
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
return(0)
}
# If the dimension is 0 then the points are considered equal.
if (dimensions == 0) {
warning("The dimension is 0.")
return(min(length1, length2))
}
# Setting the default point spacing if required.
if (pointSpacing < 0) {
pointSpacing <- length1
if (length1 < length2) {
pointSpacing <- length2
}
}
# Calculating the subsets of translations.
translations <- list()
for (d in 1:dimensions) {
translations[[d]] <- TranslationSubset(traj1[, d], traj2[, d],
pointSpacing, pointDistance)
}
# Storing the most optimal translations and similarity found so far.
similarity <- LCSSCalc(traj1, traj2, pointSpacing, pointDistance)
optimalTrans <- rep(0.0, dimensions)
similarity <- c(similarity, optimalTrans)
# Calculating how many translation possibilities are skipped for
# every one that is checked using the error margin given.
spacing <- length(translations[[1]]) / (4 * pointSpacing / errorMarg)
if (spacing < 1) {
spacing <- 1
} else if (spacing > (length(translations[[1]]) / 2.0)) {
spacing <- length(translations[[1]]) / 2.0
}
spacing <- as.integer(spacing)
# Running the LCSS algorithm on each of the translations to be checked.
similarity <- SimLoop(traj1, traj2, pointSpacing, pointDistance, spacing,
similarity, translations, dimensions)
# Returning the similarity and translations if requested.
if (returnTrans == TRUE) {
return(similarity)
}
# Returning the best similarity found.
return(similarity[1])
}

SimLoop <- function(traj1, traj2, pointSpacing, pointDistance, spacing,
similarity, translations, dimensions, dimLeft=dimensions,
currentTrans=rep(0.0, dimensions)) {
# Function to loop over and test the trajectories using the different
# translations in each dimension (this should not be called directly).
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#   spacing: The integer spacing between each translation that will
#            be tested.
#   similarity: A vector containing the current best similarity and
#               translations calculated.
#   translations: A list of vectors containing the translations in
#                 each dimension.
#   dimensions: An integer representing the number of dimensions being
#               used for the calculation.
#   dimLeft: An integer number of dimensions which have not been
#            looped over yet.
#   currentTrans: A vector containing the current translation being tested.
#
# Returns:
#   Returns the current best LCSS value and the translations that created
#   this as a vector.

# Testing each translation in this dimension.
thisDim <- 1 + dimensions - dimLeft
prevTrans <- NULL
for (i in seq(spacing, length(translations[[thisDim]]), by=spacing)) {
currentTrans[thisDim] <- translations[[thisDim]][round(i)]
# Skipping translations which have already been checked.
if (!(isTRUE(all.equal(currentTrans[thisDim], prevTrans,
tolerance=.Machine\$double.eps * 1000)))) {
if (dimLeft > 1) {
similarity <- SimLoop(traj1, traj2, pointSpacing, pointDistance,
spacing, similarity, translations,
dimensions, (dimLeft - 1), currentTrans)
} else {
# Running the LCSS algorithm on each of the translations to
# be checked.
newValue <- LCSSCalc(traj1, traj2, pointSpacing, pointDistance,
currentTrans)
# Keeping the new similarity and translations if they are better than
# the previous best.
if (newValue > similarity[1]) {
similarity[1] <- newValue
for (d in 1:dimensions) {
similarity[(d + 1)] <- currentTrans[d]
}
}
}
prevTrans <- currentTrans[thisDim]
}
}
# Returning the vector containing the current best similarity
# with translations.
return(similarity)
}

LCSSCalc <- function(traj1, traj2, pointSpacing=-1, pointDistance=20,
trans=rep(0.0, (dim(traj1)[2]))) {
# Function to calculate the LCSS of two trajectories using a set translation.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#   trans: A vector containing translations in each dimension to be applied
#          to trajectory2 in this calculation.
#
# Returns:
#   An integer value is returned. This represents the maximum LCSS value
#   obtained using the variables provided. If a problem occurs, then a
#   string containing information about the problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating the number of points in each trajectory and the dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a trajectory has no points then there are 0 similar points.
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
return(0)
}
# If the dimension is 0 then the points are considered equal.
if (dimensions == 0) {
warning("The dimension is 0.")
return(min(length1, length2))
}
# Setting the default point spacing if required.
if (pointSpacing < 0) {
pointSpacing <- length1
if (length1 < length2) {
pointSpacing <- length2
}
}
# Rounding the point spacing if necessary.
pointSpacing <- round(pointSpacing)

distMatrix <- matrix(0, nrow=length1, ncol=length2)
similarity <- 0
for (row in 1:length1) {
# Calculating the relevant columns for each row.
minCol <- 1
maxCol <- length2
if (row > pointSpacing + 1) {
minCol <- row - pointSpacing
}
if (row < length2 - pointSpacing) {
maxCol <- row + pointSpacing
}
if (minCol <= maxCol) {
for (col in minCol:maxCol) {
newValue <- 0
finalValue <- 0
# Calculating the new LCSS value for the current two points.
# Checking the diagonal.
if (row != 1 & col != 1) {
newValue <- distMatrix[row - 1, col - 1]
finalValue <- newValue
}
# Checking below.
if (row != 1) {
below <- distMatrix[row - 1, col]
if (below > finalValue) {
finalValue <- below
}
}
# Checking to the left.
if (col != 1) {
before <- distMatrix[row, col - 1]
if (before > finalValue) {
finalValue <- before
}
}
# Checking if the current points can increment the LCSS.
if (finalValue < newValue + 1) {
checkPoint <- DistanceCheck(traj1[row, ], (traj2[col, ] + trans),
pointDistance, dimensions)
if (checkPoint) {
newValue <- newValue + 1
finalValue <- newValue
}
}
# Updating the distance matrix.
distMatrix[row, col] <- finalValue
# Updating the similarity if a new maximum has been found.
if (finalValue > similarity) {
similarity <- finalValue
}
}
}
}
# Returning the largest similarity.
return(similarity)
}

LCSSRatioCalc <- function(traj1, traj2, pointSpacing=-1, pointDistance=20,
trans=rep(0.0, (dim(traj1)[2]))) {
# A function to calculate the LCSS ratio between two trajectories using
# a set translation.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#   trans: A vector containing translations in each dimension to be applied
#          to trajectory2 in this calculation.
#
# Returns:
#   A floating point value is returned. This represents the maximum LCSS
#   ratio obtained using the variables provided. If a problem occurs, then
#   a string containing information about the problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating the number of points in each trajectory and the dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a trajectory has no points then the ratio is 0.
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
return(0.0)
}
# Calculating the ratio based on the shortest trajectory.
length <- min(length1, length2)
return(LCSSCalc(traj1, traj2, pointSpacing, pointDistance,
trans) * 1.0 / length)
}

LCSSTranslation <- function(traj1, traj2, pointSpacing=-1, pointDistance=20,
errorMarg=2) {
# A function for returning the best translation calculated using the
# LCSS method.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#   errorMarg: A floating point error margin used to scale the accuracy
#              and speed of the calculation.
#
# Returns:
#   A vector of length n is returned containing the translation in each
#   dimension. If there is a problem this returns a string of information

# Running the LCSS method.
values <- LCSS(traj1, traj2, pointSpacing, pointDistance,
errorMarg, TRUE)
# Returning the translations as a vector.
return(values[2:(length(values))])
}

TranslationSubset <- function(traj1, traj2, pointSpacing, pointDistance) {
# A function for calculating the subsets of translations to be tested
# using the LCSS methods (this should not be called directly).
#
# Args:
#   traj1: A vector containing one dimension of trajectory1.
#   traj2: A vector containing one dimension of trajectory2.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#
# Returns:
#   A vector of floating point numbers is returned containing the
#   translations calculated. This vector is sorted in ascending order.

# Calculating the lengths of the trajectories.
length1 <- length(traj1)
length2 <- length(traj2)
translations <- vector()
for (row in 1:length1) {
# Calculating the relevant columns for each row.
minCol <- 1
maxCol <- length2
if (row > pointSpacing + 1) {
minCol <- row - pointSpacing
}
if (row < length2 - pointSpacing) {
maxCol <- row + pointSpacing
}
if (minCol <= maxCol) {
for (col in minCol:maxCol) {
# Adding the new translations calculated from the distance boundaries.
translations <- c(translations, (traj1[row] - traj2[col] +
pointDistance))
translations <- c(translations, (traj1[row] - traj2[col] -
pointDistance))
}
}
}
# Returning the translations as a sorted vector.
return(sort(translations))
}

Frechet <- function(traj1, traj2, testLeash=-1.0) {
# A function to calculate the Frechet distance between two trajectories.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   testLeash: A numeric leash value, which if positive, checks whether
#              the leash can be used between the two trajectories. If this
#              value is negative, then it is not used and the standard
#              calculation is performed.
#
# Returns:
#   A floating point value representing the Frechet distance is returned.
#   If a test leash is given, then a boolean value is returned as true if
#   the leash was successful and false if not. If a problem occurs, then a
#   string containing information about the problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating lengths and dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a length is 0 then a trajectory has no points.
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
return("At least one trajectory contains 0 points.")
}
# If the dimension is 0 then the points are considered equal.
if (dimensions == 0) {
warning("The dimension is 0.")
return(0.0)
}
# Calculation if either trajectory has only one point.
if (length1 == 1 | length2 == 1) {
leash <- SinglePointCalc(traj1, traj2)
if (testLeash >= 0) {
if (testLeash >= leash) {
return(TRUE)
} else {
return(FALSE)
}
} else {
return(leash)
}
}
dist1 <- rep(0, length1 - 1)
dist2 <- rep(0, length2 - 1)

# Computing the distances between each point and the next for
# both trajectories.
for (point in 1:(length1 - 1)) {
dist <- DistanceSq(traj1[point + 1, ], traj1[point, ], dimensions)
dist1[point] <- sqrt(dist)
}
for (point in 1:(length2 - 1)) {
dist <- DistanceSq(traj2[point + 1, ], traj2[point, ], dimensions)
dist2[point] <- sqrt(dist)
}

# Checking the distances at the end points for the minimum leash
# possibility.
minLeashSq <- DistanceSq(traj1[1, ], traj2[1, ], dimensions)
endDistSq <- DistanceSq(traj1[length1, ], traj2[length2, ], dimensions)
if (minLeashSq < endDistSq) {
minLeashSq <- endDistSq
}
leashList <- sqrt(minLeashSq)

# Computing the squares of all the distances between the two trajectories.
distSq12 <- matrix(0, nrow=length1, ncol=length2)
for (point1 in 1:length1) {
for (point2 in 1:length2) {
dist <- DistanceSq(traj1[point1, ], traj2[point2, ], dimensions)
distSq12[point1, point2] <- dist
# Adding in these leash possibilties because they are critical points.
if (dist > minLeashSq) {
leashList <- c(leashList, sqrt(dist))
}
}
}
# If test leash is being used.
if (testLeash >= 0) {
return(FrechetCheck(traj1, traj2, testLeash, dist1, dist2, distSq12))
}
# Adding critical point leash possibilities to the leash list.
for (point1 in 1:(length1 - 1)) {
# Creating a unit vector in the direction of the next point from point1.
unitV1 <- rep(0, times=dimensions)
if (dist1[point1] != 0) {
unitV1 <- 1.0 * (traj1[point1 + 1, ] - traj1[point1, ]) /
(dist1[point1])
}
for (point2 in 1:length2) {
# Creating a vector from point1 to point 2.
vect12 <- traj2[point2, ] - traj1[point1, ]
# Dot product finds how far from point1 the closest point on the
# line is.
pointDist <- Dot(unitV1, vect12, dimensions)
# Squaring for easy calculations
pointDistSq <- pointDist * pointDist
# The square of the distance between the line segment and the point.
shortDist <- distSq12[point1, point2] - pointDistSq
leashSq <- 0.0
if (pointDist < 0) {
# This case is accounted for earlier.
} else if (pointDist > dist1[point1]) {
leashSq <- distSq12[(point1 + 1), point2]
} else {
leashSq <- shortDist
}
# Adding the leash possibility to the list.
if (leashSq > minLeashSq) {
leashList <- c(leashList, sqrt(leashSq))
}

}
}

for (point2 in 1:(length2 - 1)) {
# Creating a unit vector in the direction of the next point from point2.
unitV1 <- rep(0, times=dimensions)
if (dist2[point2] != 0) {
unitV1 <- 1.0 * (traj2[point2 + 1, ] - traj2[point2, ]) /
(dist2[point2])
}
for (point1 in 1:length1) {
# Creating a vector from point2 to point 1.
vect12 <- traj1[point1, ] - traj2[point2, ]
# Dot product finds how far from point2 the closest point on the
# line is.
pointDist <- Dot(unitV1, vect12, dimensions)
# Squaring for easy calculations
pointDistSq <- pointDist * pointDist
# The square of the distance between the line segment and the point.
shortDist <- distSq12[point1, point2] - pointDistSq
leashSq <- 0.0
if (pointDist < 0) {
# This case is accounted for earlier.
} else if (pointDist > dist2[point2]) {
leashSq <- distSq12[point1, (point2 + 1)]
} else {
leashSq <- shortDist
}
# Adding the leash possibility to the list.
if (leashSq > minLeashSq) {
leashList <- c(leashList, sqrt(leashSq))
}

}
}

# Calculating the critical points where new passages may open.
if (length1 > 3) {
for (point2 in 1:(length2 - 1)) {
# Creating a unit vector in the direction of the next point from point1.
unitV2 <- rep(0, times=dimensions)
if (dist2[point2] != 0) {
unitV2 <- 1.0 * (traj2[point2 + 1, ] - traj2[point2, ]) /
(dist2[point2])
}
for (point1 in 2:(length1 - 2)) {
# Creating a vector from point 2 to point 1.
vect21 <- traj1[point1, ] - traj2[point2, ]
# Dot product finds how far from point2 the closest point on the
# line is.
pointDist <- Dot(unitV2, vect21, dimensions)
if (pointDist > 0) {
# Squaring for easy calculations
pointDistSq <- pointDist * pointDist
# The square of the distance between the line segment and the point.
shortDist <- distSq12[point1, point2] - pointDistSq
# The second point where the passage opens up.
for (newPoint in (point1 + 1):(length1 - 1)) {
# Creating a vector from point 2 to newPoint.
vect2new <- traj1[newPoint, ] - traj2[point2, ]
# Dot product finds how far from point2 the closest point on the
# line is.
newPointDist <- Dot(unitV2, vect2new, dimensions)
if (newPointDist < pointDist) {
newPointDistSq <- newPointDist * newPointDist
newShortDist <- distSq12[newPoint, point2] - newPointDistSq
# The distance between the two closest points on the line.
pointDiff <- pointDist - newPointDist
# Finding the point where the passage opens.
equivPoint <- (pointDiff * pointDiff + shortDist -
newShortDist) / (2.0 * pointDiff)
if (equivPoint > 0 & equivPoint < dist2[point2]) {
# Adding this leash to the list of possible leashes.
leashSq <- newShortDist + equivPoint * equivPoint
if (leashSq > minLeashSq) {
leashList <- c(leashList, sqrt(leashSq))
}
}
}
}
}
}
}
}
if (length2 > 3) {
for (point1 in 1:(length1 - 1)) {
# Creating a unit vector in the direction of the next point from point2.
unitV1 <- rep(0, times=dimensions)
if (dist1[point1] != 0) {
unitV1 <- 1.0 * (traj1[point1 + 1, ] - traj1[point1, ]) /
(dist1[point1])
}
for (point2 in 2:(length2 - 2)) {
# Creating a vector from point 1 to point 2.
vect12 <- traj2[point2, ] - traj1[point1, ]
# Dot product finds how far from point1 the closest point on the
# line is.
pointDist <- Dot(unitV1, vect12, dimensions)
if (pointDist > 0) {
# Squaring for easy calculations
pointDistSq <- pointDist * pointDist
# The square of the distance between the line segment and the point.
shortDist <- distSq12[point1, point2] - pointDistSq
# The second point where the passage opens up.
for (newPoint in (point2 + 1):(length2 - 1)) {
# Creating a vector from point 1 to newPoint.
vect1new <- traj2[newPoint, ] - traj1[point1, ]
# Dot product finds how far from point1 the closest point on the
# line is.
newPointDist <- Dot(unitV1, vect1new, dimensions)
if (newPointDist < pointDist) {
newPointDistSq <- newPointDist * newPointDist
newShortDist <- distSq12[point1, newPoint] - newPointDistSq
# The distance between the two closest points on the line.
pointDiff <- pointDist - newPointDist
# Finding the point where the passage opens.
equivPoint <- (pointDiff * pointDiff + shortDist -
newShortDist) / (2.0 * pointDiff)
if (equivPoint > 0 & equivPoint < dist1[point1]) {
# Adding this leash to the list of possible leashes.
leashSq <- newShortDist + equivPoint * equivPoint
if (leashSq > minLeashSq) {
leashList <- c(leashList, sqrt(leashSq))
}
}
}
}
}
}
}
}
# Sorting the leash list and removing multiples.
leashList <- sort(leashList)
uniqLeash <- leashList[1]
lastLeash <- uniqLeash
for (item in leashList) {
if (lastLeash != item) {
lastLeash <- item
uniqLeash <- c(uniqLeash, item)
}
}
# Setting up binary search for the list.
startSearch <- 1
endSearch <- length(uniqLeash)
# Making sure that the final leash is large enough.
if (FrechetCheck(traj1, traj2, uniqLeash[endSearch], dist1, dist2,
distSq12)) {
# Performing binary search on the list of leashes.
while (startSearch < endSearch) {
current <- (as.integer((endSearch - startSearch) / 2) + startSearch)
if (FrechetCheck(traj1, traj2, uniqLeash[current], dist1, dist2,
distSq12)) {
endSearch <- current
} else {
startSearch <- current + 1
}
}
# Returning the shortest leash for the trajectories.
return(uniqLeash[endSearch])
} else {
warning("The Frechet distance was unable to be found")
return(-1)
}
}

DistanceSq <- function(point1, point2, dimensions=length(point1)) {
# A function to calculate the square of the distance between two points
# with the same dimensions.
#
# Args:
#   point1: An n dimensional vector representing point1.
#   point2: An n dimensional vector representing point2.
#   dimensions: An integer representing the number of dimensions being
#               used for the distance square calculation. This defaults
#               to the length of the first vector.
#
# Returns:
#   A floating point value is returned, representing the square of the
#   distance between the two points.

dist <- 0.0
# Adding on the square from each dimension.
for (d in 1:dimensions) {
dist <- dist + (point1[d] - point2[d])^2
}
return(dist)
}

DistanceCheck <- function(point1, point2, dist, dimensions=length(point1)) {
# A function to check whether two points lie within some distance
# in every dimension.
#
# Args:
#   point1: An n dimensional vector representing point1.
#   point2: An n dimensional vector representing point2.
#   dist: A floating point number representing the maximum distance in
#         each dimension allowed for points to be considered equivalent.
#   dimensions: An integer representing the number of dimensions being
#               checked. This defaults to the length of the first vector.
#
# Returns:
#   A boolean value is returned. The value is true if the points are
#   within the distance in every dimension and false if not.

# Initialising to TRUE which is returned if the check is successful.
check <- TRUE
for (d in 1:dimensions) {
if (check) {
newDist <- abs(point1[d] - point2[d])
# If a the points are not within the distance in a dimension
# then FALSE is returned.
if (!(isTRUE(all.equal(newDist, dist,
tolerance=.Machine\$double.eps * 1000)))
& newDist > dist) {
check <- FALSE
}
}
}
# Returning the boolean value.
return(check)
}

SinglePointCalc <- function(traj1, traj2) {
# A function to calculate the Frechet distance when one trajectory
# contains only a single point (this should not be called directly).
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#
# Returns:
#   A floating point value representing the Frechet distance is returned.

dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
leashSq <- -1.0
# Calculating each leash possibility.
if (length1 == 1) {
for (point2 in 1:length2) {
newLeashSq <- DistanceSq(traj1[1, ], traj2[point2, ], dimensions)
# Keeping the new leash if it is longer than the previous.
if (newLeashSq > leashSq) {
leashSq <- newLeashSq
}
}
} else if (length2 == 1) {
for (point1 in 1:length1) {
newLeashSq <- DistanceSq(traj1[point1, ], traj2[1, ], dimensions)
# Keeping the new leash if it is longer than the previous.
if (newLeashSq > leashSq) {
leashSq <- newLeashSq
}
}
}
# Returning the leash.
if (leashSq >= 0) {
return(sqrt(leashSq))
} else {
warning("Error in single point trajectory calculation.")
return("Error in single point trajectory calculation.")
}
}

FrechetCheck <- function(traj1, traj2, leash, dist1, dist2, distSq12) {
# A function to check whether a leash will work between two trajectories
# using the Frechet method (this should not be called directly).
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   leash: A numeric leash value to be checked by the function.
#   dist1: A vector containing the distance between each successive two
#          points in trajectory1.
#   dist2: A vector containing the distance between each successive two
#          points in trajectory2.
#   distSq12: A matrix containing the distance between each pair of two
#             points where 1 point lies in trajectory1 and the other
#             in trajectory2.
#
# Returns:
#   A boolean value is returned. A value of true is returned if the leash
#   is successful and false if not.

# Setting up the dimensions, lengths and required arrays.
leashSq <- leash * leash
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
left <- array(-1, dim=c(length1, (length2 - 1), 2))
bottom <- array(-1, dim=c((length1 - 1), length2, 2))
newLeft <- array(-1, dim=c(length1, (length2 - 1), 2))
newBot <- array(-1, dim=c((length1 - 1), length2, 2))
# Checking the leash works at the endpoints.
if (leashSq < distSq12[1, 1] | leashSq < distSq12[length1, length2]) {
return(FALSE)
}
# Calculating the freespace of the first trajectory with respect
# to the second.
for (point1 in 1:(length1 - 1)) {
# Creating a unit vector in the direction of the next point from point1.
unitV1 <- rep(0, times=dimensions)
if (dist1[point1] != 0) {
unitV1 <- 1.0 * (traj1[point1 + 1, ] - traj1[point1, ]) /
(dist1[point1])
}
for (point2 in 1:length2) {
# Creating a vector from point1 to point 2.
vect12 <- traj2[point2, ] - traj1[point1, ]
# Dot product finds how far from point1 the closest point on the
# line is.
pointDist <- Dot(unitV1, vect12, dimensions)
# Squaring for easy calculations
pointDistSq <- pointDist * pointDist
# The square of the distance between the line segment and the point.
shortDist <- distSq12[point1, point2] - pointDistSq
# If some part of the current line can be used by the leash.
if (shortDist <= leashSq) {
# Calculating the envelope along the line.
envSize <- sqrt(leashSq - shortDist)
envLow <- pointDist - envSize
envHigh <- pointDist + envSize
# If the whole line is within the envelope.
if (envHigh >= dist1[point1] & envLow <= 0) {
bottom[point1, point2, 1] <- 0.0
bottom[point1, point2, 2] <- 1.0
} else if (envHigh >= 0 & envLow <= 0) {
# If the start of the line is within the envelope.
bottom[point1, point2, 1] <- 0.0
bottom[point1, point2, 2] <- 1.0 * envHigh / dist1[point1]
} else if (envHigh >= dist1[point1] & envLow <= dist1[point1]) {
# If the end of the line is within the envelope.
bottom[point1, point2, 1] <- 1.0 * envLow / dist1[point1]
bottom[point1, point2, 2] <- 1.0
} else if (envHigh >= 0 & envLow <= dist1[point1]) {
# If the envelope is completely within the line.
bottom[point1, point2, 1] <- 1.0 * envLow / dist1[point1]
bottom[point1, point2, 2] <- 1.0 * envHigh / dist1[point1]
}
}
}
}
# Calculating the freespace of the second trajectory with respect
# to the first.
for (point2 in 1:(length2 - 1)) {
# Creating a unit vector in the direction of the next point from point2.
unitV1 <- rep(0, times=dimensions)
if (dist2[point2] != 0) {
unitV1 <- 1.0 * (traj2[point2 + 1, ] - traj2[point2, ]) /
(dist2[point2])
}
for (point1 in 1:length1) {
# Creating a vector from point2 to point 1.
vect12 <- traj1[point1, ] - traj2[point2, ]
# Dot product finds how far from point2 the closest point on the
# line is.
pointDist <- Dot(unitV1, vect12, dimensions)
# Squaring for easy calculations
pointDistSq <- pointDist * pointDist
# The square of the distance between the line segment and the point.
shortDist <- distSq12[point1, point2] - pointDistSq
# If some part of the current line can be used by the leash.
if (shortDist <= leashSq) {
# Calculating the envelope along the line.
envSize <- sqrt(leashSq - shortDist)
envLow <- pointDist - envSize
envHigh <- pointDist + envSize
# If the whole line is within the envelope.
if (envHigh >= dist2[point2] & envLow <= 0) {
left[point1, point2, 1] <- 0.0
left[point1, point2, 2] <- 1.0
} else if (envHigh >= 0 & envLow <= 0) {
# If the start of the line is within the envelope.
left[point1, point2, 1] <- 0.0
left[point1, point2, 2] <- 1.0 * envHigh / dist2[point2]
} else if (envHigh >= dist2[point2] & envLow <= dist2[point2]) {
# If the end of the line is within the envelope.
left[point1, point2, 1] <- 1.0 * envLow / dist2[point2]
left[point1, point2, 2] <- 1.0
} else if (envHigh >= 0 & envLow <= dist2[point2]) {
# If the envelope is completely within the line.
left[point1, point2, 1] <- 1.0 * envLow / dist2[point2]
left[point1, point2, 2] <- 1.0 * envHigh / dist2[point2]
}
}
}
}
# Setting up the new arrays to find the monotone freespace.
newLeft[1, 1, 1] <- left[1, 1, 1]
newLeft[1, 1, 2] <- left[1, 1, 2]
newBot[1, 1, 1] <- bottom[1, 1, 1]
newBot[1, 1, 2] <- bottom[1, 1, 2]
# Setting the first line of the new left array.
if (length2 > 2) {
for (point2 in 2:(length2 - 1)) {
if (newLeft[1, (point2 - 1), 2] == 1) {
newLeft[1, point2, 1] <- left[1, point2, 1]
newLeft[1, point2, 2] <- left[1, point2, 2]
}
}
}
# Setting the first line of the new bottom array.
if (length1 > 2) {
for (point1 in 2:(length1 - 1)) {
if (newBot[(point1 - 1), 1, 2] == 1) {
newBot[point1, 1, 1] <- bottom[point1, 1, 1]
newBot[point1, 1, 2] <- bottom[point1, 1, 2]
}
}
}
# Calculating the monotone freespace
for (point1 in 1:length1) {
for (point2 in 1:length2) {
if (point1 != length1 & point2 != 1) {
# If the area is allowable from the freespace.
if (bottom[point1, point2, 1] > -0.1) {
# If the area can be entered from the left.
if (newLeft[point1, (point2 - 1), 1] > -0.1) {
# Setting the monotone freespace for these points.
newBot[point1, point2, 1] <- bottom[point1, point2, 1]
newBot[point1, point2, 2] <- bottom[point1, point2, 2]
} else if (newBot[point1, (point2 - 1), 1] > -0.1) {
# Otherwise using the new bottom array.
# Setting the monotone freespace according to what is reachable.
if (newBot[point1, (point2 - 1), 1] <=
bottom[point1, point2, 1]) {
newBot[point1, point2, 1] <- bottom[point1, point2, 1]
newBot[point1, point2, 2] <- bottom[point1, point2, 2]
} else if (newBot[point1, (point2 - 1), 1] <=
bottom[point1, point2, 2]) {
newBot[point1, point2, 1] <- newBot[point1, (point2 - 1), 1]
newBot[point1, point2, 2] <- bottom[point1, point2, 2]
}
}
}
}
if (point2 != length2 & point1 != 1) {
# If the area is allowable from the freespace.
if (left[point1, point2, 1] > -0.1) {
# If the area can be entered from below.
if (newBot[(point1 - 1), point2, 1] > -0.1) {
# Setting the monotone freespace for these points.
newLeft[point1, point2, 1] <- left[point1, point2, 1]
newLeft[point1, point2, 2] <- left[point1, point2, 2]
} else if (newLeft[(point1 - 1), point2, 1] > -0.1) {
# Otherwise using the new left array.
# Setting the monotone freespace according to what is reachable.
if (newLeft[(point1 - 1), point2, 1] <=
left[point1, point2, 1]) {
newLeft[point1, point2, 1] <- left[point1, point2, 1]
newLeft[point1, point2, 2] <- left[point1, point2, 2]
} else if (newLeft[(point1 - 1), point2, 1] <=
left[point1, point2, 2]) {
newLeft[point1, point2, 1] <- newLeft[(point1 - 1), point2, 1]
newLeft[point1, point2, 2] <- left[point1, point2, 2]
}
}
}
}
}
}
# If the monotone freespace reaches the final point then
# the leash is successful.
if (newLeft[length1, (length2 - 1), 2] == 1 |
newBot[(length1 - 1), length2, 2] == 1) {
return(TRUE)
} else {
# Otherwise the leash is unsuccessful.
return(FALSE)
}
}

DTW <- function(traj1, traj2, pointSpacing=-1) {
# A function for calculating the Dynamic Time Warping value between
# two trajectories.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointSpacing: An integer value of the maximum index difference between
#                 trajectory1 and trajectory2 allowed in the calculation.
#                 A negative value sets the point spacing to unlimited.
#
# Returns:
#   A floating point value representing the smallest warp path is returned.
#   If a problem occurs, then a string containing information about the
#   problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# Checking lengths and dimensions.
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
return("At least one trajectory contains 0 points.")
}
if (dimensions == 0) {
warning("The dimension is 0.")
return("The dimension is 0.")
}
# Setting the default point spacing if required.
if (pointSpacing < 0) {
pointSpacing <- length1
if (length1 < length2) {
pointSpacing <- length2
}
}
# Initialising the matrices required.
warpPaths <- matrix(-1.0, nrow=length1, ncol=length2)
dist <- DistanceSq(traj1[1, ], traj2[1, ], dimensions)
warpPaths[1, 1] <- sqrt(dist)
# Setting the first row and column of the warp path matrix.
if (length1 > 1 & pointSpacing > 0) {
for (point in 2:(min(length1, (pointSpacing + 1)))) {
dist <- DistanceSq(traj1[point, ], traj2[1, ], dimensions)
warpPaths[point, 1] <- sqrt(dist) + warpPaths[(point - 1), 1]
}
}
if (length2 > 1 & pointSpacing > 0) {
for (point in 2:(min(length2, (pointSpacing + 1)))) {
dist <- DistanceSq(traj1[1, ], traj2[point, ], dimensions)
warpPaths[1, point] <- sqrt(dist) + warpPaths[1, (point - 1)]
}
}
# Setting the rest of the warp path matrix.
if (length1 > 1 & length2 > 1 & pointSpacing >= 0) {
for (point1 in 2:length1) {
for (point2 in 2: length2) {
pointDiff <- point1 - point2
# When the points are within point distance.
if (abs(pointDiff) <= pointSpacing) {
# Calculating the square of the distance between the points.
dist <- DistanceSq(traj1[point1, ], traj2[point2, ], dimensions)
path <- -1.0
# When no point spacing is allowed.
if (pointSpacing == 0) {
path <- warpPaths[(point1 - 1), (point2 - 1)]
} else if (pointDiff == pointSpacing) {
# The furthest distance forward point calculation.
path <- min(warpPaths[(point1 - 1), (point2 - 1)],
warpPaths[(point1 - 1), point2])
} else if ((-pointDiff) == pointSpacing) {
# The furthest distance backwards point calculation.
path <- min(warpPaths[(point1 - 1), (point2 - 1)],
warpPaths[point1, (point2 - 1)])
} else {
# All of the other points.
path <- min(warpPaths[(point1 - 1), (point2 - 1)],
warpPaths[point1, (point2 - 1)],
warpPaths[(point1 - 1), point2])
}
# Storing the new best path for these points.
warpPaths[point1, point2] <- path + sqrt(dist)
}
}
}
}
# Returning the best warp path distance.
return(warpPaths[length1, length2])
}

EditDist <- function(traj1, traj2, pointDistance=20) {
# A function for calculating the Edit Distance between two trajectories.
# This takes two trajectories and the maximum distance between points.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#   pointDistance: A floating point number representing the maximum
#                  distance in each dimension allowed for points to be
#                  considered equivalent.
#
# Returns:
#   An integer representing the minimum number of edits required is
#   returned. If a problem occurs, then a string containing information
#   about the problem is returned.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating dimension and lengths.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# When one trajectory has no points.
if (length1 == 0) {
warning("At least one trajectory contains 0 points.")
return(length2)
}
if (length2 == 0) {
warning("At least one trajectory contains 0 points.")
return(length1)
}
# When the dimension is 0.
if (dimensions == 0) {
warning("The dimension is 0.")
return("The dimension is 0.")
}
# Initialising the path matrix and setting up the first column and row.
editPaths <- matrix(-1, nrow=(length1 + 1), ncol=(length2 + 1))
for (point1 in 1:(length1 + 1)) {
editPaths[point1, 1] <- point1 - 1
}
for (point2 in 2:(length2 + 1)) {
editPaths[1, point2] <- point2 - 1
}
# Setting up the rest of the matrix.
for (point1 in 2:(length1 + 1)) {
for (point2 in 2:(length2 + 1)) {
# Setting the diagonal increment depending on whether the
# current points are within range.
diag <- 1
if (DistanceCheck(traj1[(point1 - 1), ], traj2[(point2 - 1), ],
pointDistance, dimensions)) {
diag <- 0
}
# Setting the path to the current two points to the minimum value.
pathValue <- min((editPaths[(point1 - 1), point2] + 1),
(editPaths[point1, (point2 - 1)] + 1),
(editPaths[(point1 - 1), (point2 - 1)] + diag))
editPaths[point1, point2] <- pathValue
}
}
# Returning the final minimum path value as the Edit Distance.
return(editPaths[(length1 + 1), (length2 + 1)])
}

Dot <- function(vect1, vect2, dimensions=length(vect1)) {
# A function to calculate dot products between two vectors.
#
# Args:
#   vect1: An n dimensional vector representing the first vector.
#   vect2: An n dimensional vector representing the second vector.
#   dimensions: An integer representing the number of dimensions being
#               used for the dot product calculation. This defaults to
#               the length of the first vector.
#
# Returns:
#   A floating point value is returned, representing the dot product
#   between the two vectors.

total <- 0.0
if (dimensions == 0) {
return(total)
}
# Calculating the dot product.
for (d in 1:dimensions) {
total <- total + vect1[d] * vect2[d]
}
return(total)
}

TrajCheck <- function(traj1, traj2) {
# A function to check that trajectories are in matrix form and have
# the same dimension.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#
# Returns:
#   If there is a problem with one of the checks then a string containing
#   information is returned. If all of the checks pass then -1 is returned.

# Checking that the trajectories are matrices.
if (!(is.matrix(traj1)) | !(is.matrix(traj2))) {
warning("At least one trajectory is not a matrix.")
return("At least one trajectory is not a matrix.")
}
# Checking trajectories have the same dimension.
if (dim(traj1)[2] != dim(traj2)[2]) {
warning("The dimension of the trajectories does not match.")
return("The dimension of the trajectories does not match.")
} else {
return(-1)
}
}

AveTranslate <- function(traj1, traj2) {
# A function to calculate a translation vector for trajectory 2 using the
# average of the two trajectories.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#
# Returns:
#   A vector of length n is returned containing the translation in each
#   dimension. If there is a problem this returns a string of information

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating the number of points in each trajectory and the dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a trajectory has no points or is dimension 0 then there is
# no translation.
if (length1 == 0 | length2 == 0 | dimensions == 0) {
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
}
if (dimensions == 0) {
warning("The dimension is 0.")
}
return(rep(0.0, dimensions))
}
# Creating a translation vector to store the translations
# of each dimension.
translation <- vector()
for (d in 1:dimensions) {
newTrans <- (mean(traj1[, d]) - mean(traj2[, d]))
translation <- c(translation, newTrans)
}
# Returning a vector of the translations.
return(translation)
}

StartEndTranslate <- function(traj1, traj2) {
# A function to calculate a variation of trajectory 2 by aligning
# its start and end points with those of trajectory 1.
#
# Args:
#   traj1: An m x n matrix containing trajectory1. Here m is the number
#          of points and n is the dimension of the points.
#   traj2: A k x n matrix containing trajectory2. Here k is the number
#          of points and n is the dimension of the points. The two
#          trajectories are not required to have the same number of points.
#
# Returns:
#   An m x n matrix containing the new variation of trajectory2 is
#   returned. Here m is the number of points and n is the dimension of
#   the points.

# Checking the trajectories.
trajTest <- TrajCheck(traj1, traj2)
if (is.character(trajTest)) {
return(trajTest)
}
# Calculating the number of points in each trajectory and the dimensions.
dimensions <- dim(traj1)[2]
length1 <- dim(traj1)[1]
length2 <- dim(traj2)[1]
# If a trajectory has no points or is dimension 0 then there is
# no translation.
if (length1 == 0 | length2 == 0 | dimensions == 0) {
if (length1 == 0 | length2 == 0) {
warning("At least one trajectory contains 0 points.")
}
if (dimensions == 0) {
warning("The dimension is 0.")
}
return(traj2)
}
newTraj <- traj2
for (d in 1:dimensions) {
diff1 <- traj1[length1, d] - traj1[1, d]
diff2 <- traj2[length2, d] - traj2[1, d]
if (diff2 == 0) {
warning("Equivalent start and end points in 1 dimension.")
} else {
for (point in 1:length2) {
pointDiff <- traj2[point, d] - traj2[1, d]
newTraj[point, d] <- (pointDiff / diff2) * diff1 + traj1[1, d]
}
}
}
return(newTraj)
}
```

## Try the SimilarityMeasures package in your browser

Any scripts or data that you put into this service are public.

SimilarityMeasures documentation built on May 29, 2017, 12:38 p.m.