function DistanciaGPS() {
    var Sin = 0;
    var Cos = 0;

    SinCos = function (Theta) {
        Sin = Math.sin(Theta);
        Cos = Math.cos(Theta);
    };
    DegToRad = function (degrees) {
        return (degrees / 180.0) * Math.PI;
    };
    Hypot = function (a, b) {
        return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));
    };

    this.LatLng = function (lat, long) {
        return {
            latitude: lat,
            longitude: long
        }
    };

    this.LeiCosenos = function (pt1, pt2) {
        if (pt1 == null || pt2 == null)
            return Number.NaN;

        var lat1 = DegToRad(pt1.latitude);
        var lon1 = DegToRad(pt1.longitude);

        var lat2 = DegToRad(pt2.latitude);
        var lon2 = DegToRad(pt2.longitude);

        return Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) * 6371 * 1000;
    };

    this.LeiCosenosGMaps = function (pt1, pt2) {
        if (pt1 == null || pt2 == null)
            return Number.NaN;

        var lat1 = DegToRad(pt1.lat());
        var lon1 = DegToRad(pt1.lng());

        var lat2 = DegToRad(pt2.lat());
        var lon2 = DegToRad(pt2.lng());

        return Math.acos(Math.sin(lat1) * Math.sin(lat2) + Math.cos(lat1) * Math.cos(lat2) * Math.cos(lon2 - lon1)) * 6371 * 1000;
    };

    this.DistanciaRetaPonto = function (pt1, pt2, pontoComparacao) {
        var deltax, deltay, incReta, distancia;
        var pontoIntersecao = this.LatLng();

        deltax = pt2.longitude - pt1.longitude;
        deltay = pt2.latitude - pt1.latitude;

        incReta = (pontoComparacao.longitude - pt1.longitude) * deltax + (pontoComparacao.latitude - pt1.latitude) * deltay;

        if ((deltax * deltax) + (deltay * deltay) > 0)
            incReta /= ((deltax * deltax) + (deltay * deltay));
        else
            incReta = -1;

        if (incReta < 0) {
            pontoIntersecao = pt1;
        }
        else if (incReta > 1) {
            pontoIntersecao = pt2;
        }
        else {
            pontoIntersecao.Longitude = pt1.longitude + incReta * deltax;
            pontoIntersecao.Latitude = pt1.latitude + incReta * deltay;
        }

        distancia = this.LeiCosenos(pontoIntersecao, pontoComparacao);

        return distancia;

    };

    this.DistanciaRetaPontoMap = function (pt1, pt2, pontoComparacao) {
        var deltax, deltay, incReta, distancia;
        var pontoIntersecao = null;//this.LatLng();

        deltax = pt2.lng() - pt1.lng();
        deltay = pt2.lat() - pt1.lat();

        incReta = (pontoComparacao.lng() - pt1.lng()) * deltax + (pontoComparacao.lat() - pt1.lat()) * deltay;

        if ((deltax * deltax) + (deltay * deltay) > 0)
            incReta /= ((deltax * deltax) + (deltay * deltay));
        else
            incReta = -1;

        if (incReta < 0) {
            pontoIntersecao = pt1;
        }
        else if (incReta > 1) {
            pontoIntersecao = pt2;
        }
        else {
            pontoIntersecao = new google.maps.LatLng(pt1.lng() + incReta * deltax, pt1.lat() + incReta * deltay);
        }

        distancia = this.LeiCosenosGMaps(pontoIntersecao, pontoComparacao);

        return distancia;

    };

    this.EncontraPontoMaisProximo = function (arrayPontos, pontoComparacao) {
        var deltax, deltay, incReta, distancia = (Math.pow(2, 32) / 2), distancia1, distancia2, indexRetorno;
        var pontoIntersecao = new google.maps.LatLng(0, 0);

        // Definição do limite máximo da distância válida para começar ou terminar o DESVIO em relação ao itinerário
        var DISTANCIA_MAXIMA_ENTRE_PONTOS = 40;

        var pt1 = arrayPontos[0];

        indexRetorno = 0;

        for (var index = 1; index < arrayPontos.length; index++) {

            pt2 = arrayPontos[index];

            deltax = pt2.lng() - pt1.lng();
            deltay = pt2.lat() - pt1.lat();

            incReta = ((pontoComparacao.lng() - pt1.lng()) * deltax) + (pontoComparacao.lat() - pt1.lat()) * deltay;

            if ((deltax * deltax) + (deltay * deltay) > 0)
                incReta /= ((deltax * deltax) + (deltay * deltay));
            else
                incReta = -1;

            if (incReta < 0) {
                pontoIntersecao = pt1;
            }
            else if (incReta > 1) {
                pontoIntersecao = pt2;
            }
            else {
                pontoIntersecao = new google.maps.LatLng((pt1.lat() + incReta * deltay), (pt1.lng() + incReta * deltax));
            }

            distancia1 = this.LeiCosenosGMaps(pontoIntersecao, pt1);
            distancia2 = this.LeiCosenosGMaps(pontoIntersecao, pt2);

            var dist = this.LeiCosenosGMaps(pontoIntersecao, pontoComparacao);

            if (dist < distancia)
                distancia = dist;

            if (distancia1 > distancia2) {
                distancia1 = distancia2;
                pt1 = pt2;
                indexRetorno = index;
            }
        }

        if (distancia > DISTANCIA_MAXIMA_ENTRE_PONTOS) {
            return -1;
        }
        else {
            return indexRetorno;
        }
    };

    this.SimplificarPath = function (PointList, epsilon) {
        // Find the point with the maximum distance
        var dmax = 0;
        var index = 0;
        var end = PointList.length - 1;
        for (var i = 2 ; i < (end - 1) ; i++) {
            d = this.DistanciaRetaPontoMap(PointList[1], PointList[end], PointList[i]);
            if (d > dmax) {
                index = i;
                dmax = d;
            }
        }
        // If max distance is greater than epsilon, recursively simplify
        if (dmax > epsilon) {
            // Recursive call
            var recResults1 = this.SimplificarPath(PointList.splice(1, index - 1), epsilon);
            var recResults2 = this.SimplificarPath(PointList.splice(index, end - index), epsilon);

            // Build the result list
            return recResults1.concat(recResults2);
        } else {
            return [PointList[1], PointList[end]];
        }
    };
}