List of usage examples for org.apache.commons.math3.geometry.euclidean.threed SphericalCoordinates SphericalCoordinates
public SphericalCoordinates(final Vector3D v)
From source file:fr.amap.lidar.format.jleica.LPointShotExtractor.java
public LPointShotExtractor(GriddedPointScan scan) throws Exception { this.scan = scan; scan.computeExtremumsAngles();/* w w w.j av a 2s . c o m*/ scan.openScanFile(scan.getFile()); angles = new SimpleSpherCoords[this.scan.getHeader().getNumRows()][this.scan.getHeader().getNumCols()]; azimuts = new boolean[this.scan.getHeader().getNumCols()]; zenithals = new boolean[this.scan.getHeader().getNumRows()]; //azimutsRegression = new SimpleRegression[this.scan.getHeader().getNumCols()]; //zenithalsRegression = new SimpleRegression[this.scan.getHeader().getNumRows()]; //azimuts = new SimpleRegression[this.scan.getHeader().getNumCols()]; //zenithals = new SimpleRegression[this.scan.getHeader().getNumRows()]; Iterator<LPoint> iterator = scan.iterator(); while (iterator.hasNext()) { LPoint point = iterator.next(); if (point.valid) { double x, y, z; if (scan.getHeader().isPointInDoubleFormat()) { x = ((LDoublePoint) point).x; y = ((LDoublePoint) point).y; z = ((LDoublePoint) point).z; } else { x = ((LFloatPoint) point).x; y = ((LFloatPoint) point).y; z = ((LFloatPoint) point).z; } Vector3d dir = new Vector3d(x, y, z); dir.normalize(); SphericalCoordinates sc = new SphericalCoordinates(new Vector3D(dir.x, dir.y, dir.z)); angles[point.rowIndex][point.columnIndex] = new SimpleSpherCoords(); angles[point.rowIndex][point.columnIndex].azimut = sc.getTheta(); angles[point.rowIndex][point.columnIndex].zenith = sc.getPhi(); azimuts[point.columnIndex] = true; zenithals[point.rowIndex] = true; } } int lastValidRowIndex = -1; int lastValidColumnIndex = -1; for (int row = 0; row < angles.length; row++) { for (int column = 0; column < angles[0].length; column++) { if (angles[row][column] == null) { double azimut = Double.NaN; double zenithal = Double.NaN; if (azimuts[column]) { for (int i = row + 1, j = row - 1; i < angles.length || j >= 0; i++, j--) { if (i < angles.length && angles[i][column] != null) { azimut = angles[i][column].azimut; azimuts[column] = true; break; } if (j >= 0 && angles[j][column] != null) { azimut = angles[j][column].azimut; azimuts[column] = true; break; } } } if (azimuts[column]) { for (int i = row + 1, j = row - 1; i < angles.length || j >= 0; i++, j--) { if (i < angles.length && angles[i][column] != null) { zenithal = (angles[i][column].zenith + ((i - row) * scan.getElevationStepAngle())); azimuts[column] = true; break; } if (j >= 0 && angles[j][column] != null) { zenithal = (angles[j][column].zenith - ((row - j) * scan.getElevationStepAngle())); azimuts[column] = true; break; } } } /*if(zenithals[row]){ for(int i=column+1, j=column-1;i<angles[0].length || j>=0;i++, j--){ if(i<angles[0].length && angles[row][i] != null){ zenithal = angles[row][i].zenith; zenithals[row] = true; break; } if(j >=0 && angles[row][j] != null){ zenithal = angles[row][j].zenith; zenithals[row] = true; break; } } }*/ if (Double.isNaN(azimut)) { azimut = (scan.getAzim_min() - ((column - scan.getColIndexAzimMin()) * scan.getAzimutalStepAngle())); } if (Double.isNaN(zenithal)) { if (lastValidRowIndex != -1) { zenithal = (angles[lastValidRowIndex][lastValidColumnIndex].zenith - ((row - lastValidRowIndex) * scan.getElevationStepAngle())); } else { zenithal = (scan.getElev_min() - ((row - scan.getRowIndexElevMin()) * scan.getElevationStepAngle())); } } angles[row][column] = new SimpleSpherCoords(azimut, zenithal); } else { lastValidRowIndex = row; lastValidColumnIndex = column; } } } }
From source file:fr.amap.lidar.format.jleica.GriddedPointScan.java
/** * Compute minimum and maximum azimutal and elevation angles of the scan. *//*from www. j av a2s.c o m*/ public void computeExtremumsAngles() { Statistic minAzimutalAngle = new Statistic(); Statistic maxAzimutalAngle = new Statistic(); resetRowLimits(); resetColumnLimits(); //compute min azimutal angle int i; for (i = 0; i < header.getNumCols(); i++) { setUpColumnToRead(i); Iterator<LPoint> iterator = this.iterator(); while (iterator.hasNext()) { LPoint point = iterator.next(); SphericalCoordinates sc; if (header.isPointInFloatFormat()) { LFloatPoint floatPoint = (LFloatPoint) point; sc = new SphericalCoordinates( new Vector3D(floatPoint.x, floatPoint.y, floatPoint.z).normalize()); } else { LDoublePoint doublePoint = (LDoublePoint) point; sc = new SphericalCoordinates( new Vector3D(doublePoint.x, doublePoint.y, doublePoint.z).normalize()); } minAzimutalAngle.addValue(sc.getTheta()); } if (minAzimutalAngle.getNbValues() > 0) { break; } } azim_min = minAzimutalAngle.getMean(); colIndexAzimMin = i; //compute max azimutal angle // for (i = colIndexAzimMin+100; i < header.getNumCols(); i+=100) { // // setUpColumnToRead(i); // // Iterator<LPoint> iterator = this.iterator(); // // while(iterator.hasNext()){ // // LPoint point = iterator.next(); // // SphericalCoordinates sc = new SphericalCoordinates(); // // if(header.isPointInFloatFormat()){ // // LFloatPoint floatPoint = (LFloatPoint)point; // sc.toSpherical(new Point3d(floatPoint.x, floatPoint.y, floatPoint.z)); // }else{ // LDoublePoint doublePoint = (LDoublePoint)point; // sc.toSpherical(new Point3d(doublePoint.x, doublePoint.y, doublePoint.z)); // } // // maxAzimutalAngle.addValue(sc.getAzimuth()); // } // // if(maxAzimutalAngle.getNbValues() > 0){ // break; // } // } // // azim_max = maxAzimutalAngle.getMean(); // colIndexAzimMax = i; for (i = header.getNumCols() - 1; i >= 0; i--) { setUpColumnToRead(i); Iterator<LPoint> iterator = this.iterator(); while (iterator.hasNext()) { LPoint point = iterator.next(); SphericalCoordinates sc; if (header.isPointInFloatFormat()) { LFloatPoint floatPoint = (LFloatPoint) point; sc = new SphericalCoordinates( new Vector3D(floatPoint.x, floatPoint.y, floatPoint.z).normalize()); } else { LDoublePoint doublePoint = (LDoublePoint) point; sc = new SphericalCoordinates( new Vector3D(doublePoint.x, doublePoint.y, doublePoint.z).normalize()); } maxAzimutalAngle.addValue(sc.getTheta()); } if (maxAzimutalAngle.getNbValues() > 0) { break; } } azim_max = maxAzimutalAngle.getMean(); colIndexAzimMax = i; //compute min zenithal angle //compute max azimutal angle resetColumnLimits(); Statistic minZenithalAngle = new Statistic(); Statistic maxZenithalAngle = new Statistic(); for (i = 0; i < header.getNumRows(); i++) { setUpRowToRead(i); Iterator<LPoint> iterator = this.iterator(); while (iterator.hasNext()) { LPoint point = iterator.next(); SphericalCoordinates sc; if (header.isPointInFloatFormat()) { LFloatPoint floatPoint = (LFloatPoint) point; sc = new SphericalCoordinates( new Vector3D(floatPoint.x, floatPoint.y, floatPoint.z).normalize()); } else { LDoublePoint doublePoint = (LDoublePoint) point; sc = new SphericalCoordinates( new Vector3D(doublePoint.x, doublePoint.y, doublePoint.z).normalize()); } minZenithalAngle.addValue(sc.getPhi()); } if (minZenithalAngle.getNbValues() > 0) { break; } } elev_min = minZenithalAngle.getMean(); rowIndexElevMin = i; for (i = header.getNumRows() - 1; i >= 0; i--) { setUpRowToRead(i); Iterator<LPoint> iterator = this.iterator(); while (iterator.hasNext()) { LPoint point = iterator.next(); SphericalCoordinates sc; if (header.isPointInFloatFormat()) { LFloatPoint floatPoint = (LFloatPoint) point; sc = new SphericalCoordinates( new Vector3D(floatPoint.x, floatPoint.y, floatPoint.z).normalize()); } else { LDoublePoint doublePoint = (LDoublePoint) point; sc = new SphericalCoordinates( new Vector3D(doublePoint.x, doublePoint.y, doublePoint.z).normalize()); } maxZenithalAngle.addValue(sc.getPhi()); } if (maxZenithalAngle.getNbValues() > 0) { break; } } elev_max = maxZenithalAngle.getMean(); rowIndexElevMax = i; resetRowLimits(); resetColumnLimits(); }
From source file:fingerprint.MyStructureFingerprint.java
private LinkedHashMap<PhiThetaInterval, List<PointIfc>> groupResiduesAccordingToSolidAngleAccordingToLocalStructureBarycenter( PointIfc barycenterShape, MyStructureIfc myStructureHere) { // strongly reusing code of groupPoints List<PointIfc> listRepresentativePoints = new ArrayList<>(); for (MyChainIfc chain : myStructureHere.getAllChainsRelevantForShapeBuilding()) { for (MyMonomerIfc monomer : chain.getMyMonomers()) { float[] coords = ToolsMathAppliedToMyStructure.getCoordinatesOfRepresentativeAtom(monomer); PointIfc point = new Point(coords); listRepresentativePoints.add(point); }/*from www. j av a 2 s . com*/ } // defining 36 zones in solid space double deltaOnlyForTheta = Math.PI / 8.0; int countOfIncrementAngle = 8; // group monomers EquidistributionPhi equidistributionPhi = new EquidistributionPhi(); List<Double> phiValues = equidistributionPhi.getMapCountOfIntervalsAndPointValues() .get(countOfIncrementAngle); // theta in map ranges from -pi to +pi in agreement with apache spherical coodinates List<Double> tethaValues = ShapeReductorTools.doBinningThetaValues(deltaOnlyForTheta, countOfIncrementAngle); List<PhiThetaInterval> sectors = generateSector(deltaOnlyForTheta, phiValues, tethaValues); // create the Map to return LinkedHashMap<PhiThetaInterval, List<PointIfc>> groupPoints = new LinkedHashMap<>(); Iterator<PhiThetaInterval> it = sectors.iterator(); while (it.hasNext()) { PhiThetaInterval sector = it.next(); List<PointIfc> listPoints = new ArrayList<>(); groupPoints.put(sector, listPoints); } for (PointIfc point : listRepresentativePoints) { float[] pointRelativeToBarycenter = MathTools.v1minusV2(point.getCoords(), barycenterShape.getCoords()); Vector3D pointRelativeToBarycenterV3d = new Vector3D(pointRelativeToBarycenter[0], pointRelativeToBarycenter[1], pointRelativeToBarycenter[2]); SphericalCoordinates pointShericalRelative = new SphericalCoordinates(pointRelativeToBarycenterV3d); PhiThetaInterval intervalForThisPoint = getIntervalFromSphericalCoordinates(pointShericalRelative, sectors); if (intervalForThisPoint == null) { // it could be that some points doesnt fit so I should make the binning a bit larger I guess continue; } groupPoints.get(intervalForThisPoint).add(point); } return groupPoints; }
From source file:org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel.java
/** Compute the gradient of the non-central part of the gravity field. * @param date current date/* w ww.j a v a 2s. co m*/ * @param position position at which gravity field is desired in body frame * @return gradient of the non-central part of the gravity field * @exception OrekitException if position cannot be converted to central body frame */ public double[] gradient(final AbsoluteDate date, final Vector3D position) throws OrekitException { final int degree = provider.getMaxDegree(); final int order = provider.getMaxOrder(); final NormalizedSphericalHarmonics harmonics = provider.onDate(date); // allocate the columns for recursion double[] pnm0Plus2 = new double[degree + 1]; double[] pnm0Plus1 = new double[degree + 1]; double[] pnm0 = new double[degree + 1]; final double[] pnm1 = new double[degree + 1]; // compute polar coordinates final double x = position.getX(); final double y = position.getY(); final double z = position.getZ(); final double x2 = x * x; final double y2 = y * y; final double z2 = z * z; final double r2 = x2 + y2 + z2; final double r = FastMath.sqrt(r2); final double rho2 = x2 + y2; final double rho = FastMath.sqrt(rho2); final double t = z / r; // cos(theta), where theta is the polar angle final double u = rho / r; // sin(theta), where theta is the polar angle final double tOu = z / rho; // compute distance powers final double[] aOrN = createDistancePowersArray(provider.getAe() / r); // compute longitude cosines/sines final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho); // outer summation over order int index = 0; double value = 0; final double[] gradient = new double[3]; for (int m = degree; m >= 0; --m) { // compute tesseral terms with derivatives index = computeTesseral(m, degree, index, t, u, tOu, pnm0Plus2, pnm0Plus1, null, pnm0, pnm1, null); if (m <= order) { // compute contribution of current order to field (equation 5 of the paper) // inner summation over degree, for fixed order double sumDegreeS = 0; double sumDegreeC = 0; double dSumDegreeSdR = 0; double dSumDegreeCdR = 0; double dSumDegreeSdTheta = 0; double dSumDegreeCdTheta = 0; for (int n = FastMath.max(2, m); n <= degree; ++n) { final double qSnm = aOrN[n] * harmonics.getNormalizedSnm(n, m); final double qCnm = aOrN[n] * harmonics.getNormalizedCnm(n, m); final double nOr = n / r; final double s0 = pnm0[n] * qSnm; final double c0 = pnm0[n] * qCnm; final double s1 = pnm1[n] * qSnm; final double c1 = pnm1[n] * qCnm; sumDegreeS += s0; sumDegreeC += c0; dSumDegreeSdR -= nOr * s0; dSumDegreeCdR -= nOr * c0; dSumDegreeSdTheta += s1; dSumDegreeCdTheta += c1; } // contribution to outer summation over order // beware that we need to order gradient using the mathematical conventions // compliant with the SphericalCoordinates class, so our lambda is its theta // (and hence at index 1) and our theta is its phi (and hence at index 2) final double sML = cosSinLambda[1][m]; final double cML = cosSinLambda[0][m]; value = value * u + sML * sumDegreeS + cML * sumDegreeC; gradient[0] = gradient[0] * u + sML * dSumDegreeSdR + cML * dSumDegreeCdR; gradient[1] = gradient[1] * u + m * (cML * sumDegreeS - sML * sumDegreeC); gradient[2] = gradient[2] * u + sML * dSumDegreeSdTheta + cML * dSumDegreeCdTheta; } // rotate the recursion arrays final double[] tmp = pnm0Plus2; pnm0Plus2 = pnm0Plus1; pnm0Plus1 = pnm0; pnm0 = tmp; } // scale back value = FastMath.scalb(value, SCALING); gradient[0] = FastMath.scalb(gradient[0], SCALING); gradient[1] = FastMath.scalb(gradient[1], SCALING); gradient[2] = FastMath.scalb(gradient[2], SCALING); // apply the global mu/r factor final double muOr = mu / r; value *= muOr; gradient[0] = muOr * gradient[0] - value / r; gradient[1] *= muOr; gradient[2] *= muOr; // convert gradient from spherical to Cartesian return new SphericalCoordinates(position).toCartesianGradient(gradient); }
From source file:org.orekit.forces.gravity.HolmesFeatherstoneAttractionModel.java
/** Compute both the gradient and the hessian of the non-central part of the gravity field. * @param date current date// ww w . jav a2 s . com * @param position position at which gravity field is desired in body frame * @return gradient and hessian of the non-central part of the gravity field * @exception OrekitException if position cannot be converted to central body frame */ public GradientHessian gradientHessian(final AbsoluteDate date, final Vector3D position) throws OrekitException { final int degree = provider.getMaxDegree(); final int order = provider.getMaxOrder(); final NormalizedSphericalHarmonics harmonics = provider.onDate(date); // allocate the columns for recursion double[] pnm0Plus2 = new double[degree + 1]; double[] pnm0Plus1 = new double[degree + 1]; double[] pnm0 = new double[degree + 1]; double[] pnm1Plus1 = new double[degree + 1]; double[] pnm1 = new double[degree + 1]; final double[] pnm2 = new double[degree + 1]; // compute polar coordinates final double x = position.getX(); final double y = position.getY(); final double z = position.getZ(); final double x2 = x * x; final double y2 = y * y; final double z2 = z * z; final double r2 = x2 + y2 + z2; final double r = FastMath.sqrt(r2); final double rho2 = x2 + y2; final double rho = FastMath.sqrt(rho2); final double t = z / r; // cos(theta), where theta is the polar angle final double u = rho / r; // sin(theta), where theta is the polar angle final double tOu = z / rho; // compute distance powers final double[] aOrN = createDistancePowersArray(provider.getAe() / r); // compute longitude cosines/sines final double[][] cosSinLambda = createCosSinArrays(position.getX() / rho, position.getY() / rho); // outer summation over order int index = 0; double value = 0; final double[] gradient = new double[3]; final double[][] hessian = new double[3][3]; for (int m = degree; m >= 0; --m) { // compute tesseral terms index = computeTesseral(m, degree, index, t, u, tOu, pnm0Plus2, pnm0Plus1, pnm1Plus1, pnm0, pnm1, pnm2); if (m <= order) { // compute contribution of current order to field (equation 5 of the paper) // inner summation over degree, for fixed order double sumDegreeS = 0; double sumDegreeC = 0; double dSumDegreeSdR = 0; double dSumDegreeCdR = 0; double dSumDegreeSdTheta = 0; double dSumDegreeCdTheta = 0; double d2SumDegreeSdRdR = 0; double d2SumDegreeSdRdTheta = 0; double d2SumDegreeSdThetadTheta = 0; double d2SumDegreeCdRdR = 0; double d2SumDegreeCdRdTheta = 0; double d2SumDegreeCdThetadTheta = 0; for (int n = FastMath.max(2, m); n <= degree; ++n) { final double qSnm = aOrN[n] * harmonics.getNormalizedSnm(n, m); final double qCnm = aOrN[n] * harmonics.getNormalizedCnm(n, m); final double nOr = n / r; final double nnP1Or2 = nOr * (n + 1) / r; final double s0 = pnm0[n] * qSnm; final double c0 = pnm0[n] * qCnm; final double s1 = pnm1[n] * qSnm; final double c1 = pnm1[n] * qCnm; final double s2 = pnm2[n] * qSnm; final double c2 = pnm2[n] * qCnm; sumDegreeS += s0; sumDegreeC += c0; dSumDegreeSdR -= nOr * s0; dSumDegreeCdR -= nOr * c0; dSumDegreeSdTheta += s1; dSumDegreeCdTheta += c1; d2SumDegreeSdRdR += nnP1Or2 * s0; d2SumDegreeSdRdTheta -= nOr * s1; d2SumDegreeSdThetadTheta += s2; d2SumDegreeCdRdR += nnP1Or2 * c0; d2SumDegreeCdRdTheta -= nOr * c1; d2SumDegreeCdThetadTheta += c2; } // contribution to outer summation over order final double sML = cosSinLambda[1][m]; final double cML = cosSinLambda[0][m]; value = value * u + sML * sumDegreeS + cML * sumDegreeC; gradient[0] = gradient[0] * u + sML * dSumDegreeSdR + cML * dSumDegreeCdR; gradient[1] = gradient[1] * u + m * (cML * sumDegreeS - sML * sumDegreeC); gradient[2] = gradient[2] * u + sML * dSumDegreeSdTheta + cML * dSumDegreeCdTheta; hessian[0][0] = hessian[0][0] * u + sML * d2SumDegreeSdRdR + cML * d2SumDegreeCdRdR; hessian[1][0] = hessian[1][0] * u + m * (cML * dSumDegreeSdR - sML * dSumDegreeCdR); hessian[2][0] = hessian[2][0] * u + sML * d2SumDegreeSdRdTheta + cML * d2SumDegreeCdRdTheta; hessian[1][1] = hessian[1][1] * u - m * m * (sML * sumDegreeS + cML * sumDegreeC); hessian[2][1] = hessian[2][1] * u + m * (cML * dSumDegreeSdTheta - sML * dSumDegreeCdTheta); hessian[2][2] = hessian[2][2] * u + sML * d2SumDegreeSdThetadTheta + cML * d2SumDegreeCdThetadTheta; } // rotate the recursion arrays final double[] tmp0 = pnm0Plus2; pnm0Plus2 = pnm0Plus1; pnm0Plus1 = pnm0; pnm0 = tmp0; final double[] tmp1 = pnm1Plus1; pnm1Plus1 = pnm1; pnm1 = tmp1; } // scale back value = FastMath.scalb(value, SCALING); for (int i = 0; i < 3; ++i) { gradient[i] = FastMath.scalb(gradient[i], SCALING); for (int j = 0; j <= i; ++j) { hessian[i][j] = FastMath.scalb(hessian[i][j], SCALING); } } // apply the global mu/r factor final double muOr = mu / r; value *= muOr; gradient[0] = muOr * gradient[0] - value / r; gradient[1] *= muOr; gradient[2] *= muOr; hessian[0][0] = muOr * hessian[0][0] - 2 * gradient[0] / r; hessian[1][0] = muOr * hessian[1][0] - gradient[1] / r; hessian[2][0] = muOr * hessian[2][0] - gradient[2] / r; hessian[1][1] *= muOr; hessian[2][1] *= muOr; hessian[2][2] *= muOr; // convert gradient and Hessian from spherical to Cartesian final SphericalCoordinates sc = new SphericalCoordinates(position); return new GradientHessian(sc.toCartesianGradient(gradient), sc.toCartesianHessian(hessian, gradient)); }
From source file:shapeReduction.ShapeReductorByClustering.java
private Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> groupPoints() { double deltaOnlyForTheta = Math.PI / (double) algoParameters.getCOUNT_OF_INCREMENT_ANGLE(); int countOfIncrementAngle = algoParameters.getCOUNT_OF_INCREMENT_ANGLE(); EquidistributionPhi equidistributionPhi = new EquidistributionPhi(); List<Double> phiValues = equidistributionPhi.getMapCountOfIntervalsAndPointValues() .get(countOfIncrementAngle); // theta in map ranges from -pi to +pi in agreement with apache spherical coodinates List<Double> tethaValues = ShapeReductorTools.doBinningThetaValues(deltaOnlyForTheta, algoParameters.getCOUNT_OF_INCREMENT_ANGLE()); double maxRinThisShape = findMaxRadiusInThisShape(barycenterShape); radiusValues = doBinningRadiusValues(maxRinThisShape); SectorsIfc sectors = generateSector(deltaOnlyForTheta, phiValues, tethaValues, radiusValues); Map<PhiThetaRadiusInterval, Map<Integer, PointWithPropertiesIfc>> groupPoints = new LinkedHashMap<>(); Iterator<PhiThetaRadiusInterval> it = sectors.iterator(); while (it.hasNext()) { PhiThetaRadiusInterval sector = it.next(); Map<Integer, PointWithPropertiesIfc> collectionOfPointsWithProperties = new LinkedHashMap<>(); groupPoints.put(sector, collectionOfPointsWithProperties); }/* ww w .jav a2 s . co m*/ for (int i = 0; i < shapeCollectionPoints.getSize(); i++) { Integer pointIDToBeKept = i; float[] point = shapeCollectionPoints.getPointFromId(i).getCoords().getCoords(); float[] pointRelativeToBarycenter = MathTools.v1minusV2(point, barycenterShape.getCoords()); Vector3D pointRelativeToBarycenterV3d = new Vector3D(pointRelativeToBarycenter[0], pointRelativeToBarycenter[1], pointRelativeToBarycenter[2]); SphericalCoordinates pointShericalRelative = new SphericalCoordinates(pointRelativeToBarycenterV3d); PhiThetaRadiusInterval intervalForThisPoint = sectors .getIntervalFromSphericalCoordinates(pointShericalRelative); if (intervalForThisPoint == null) { // it could be that some points doesnt fit so I should make the binning a bit larger I guess continue; } Map<Integer, PointWithPropertiesIfc> groupWherePointToAdd = groupPoints.get(intervalForThisPoint); groupWherePointToAdd.put(pointIDToBeKept, shapeCollectionPoints.getPointFromId(i)); } return groupPoints; }