-
Notifications
You must be signed in to change notification settings - Fork 14
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
PM-Xpath implementation to calculate the distance from the boundary #1455
base: master
Are you sure you want to change the base?
Changes from 1 commit
ac492df
43b1b61
f718a08
74e90fb
7db32a6
c20fe25
4041f13
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
package org.javarosa.core.model.utils; | ||
import java.util.List; | ||
|
||
public class PolygonUtils { | ||
|
||
/** | ||
* Determines if a point is inside a polygon. | ||
* | ||
* @param polygonPoints A list of doubles representing the polygon vertices | ||
* (latitude and longitude pairs). | ||
* @param testPoint A list of doubles representing the latitude and longitude of the test point. | ||
* @return true if the point is inside the polygon, false otherwise. | ||
*/ | ||
public static boolean isPointInsidePolygon(List<Double> polygonPoints, List<Double> testPoint) { | ||
int intersectCount = 0; | ||
int vertexCount = polygonPoints.size() / 2; | ||
|
||
double testLat = testPoint.get(0); | ||
double testLng = testPoint.get(1); | ||
|
||
for (int i = 0; i < vertexCount; i++) { | ||
double lat1 = polygonPoints.get(2 * i); | ||
double lng1 = polygonPoints.get(2 * i + 1); | ||
double lat2 = polygonPoints.get((2 * ((i + 1) % vertexCount))); | ||
double lng2 = polygonPoints.get((2 * ((i + 1) % vertexCount)) + 1); | ||
|
||
if (rayIntersectsEdge(testLat, testLng, lat1, lng1, lat2, lng2)) { | ||
intersectCount++; | ||
} | ||
} | ||
|
||
return (intersectCount % 2 == 1); | ||
} | ||
|
||
/** | ||
* Checks if a ray starting from the test point intersects the edge defined by two vertices. | ||
*/ | ||
private static boolean rayIntersectsEdge(double testLat, double testLng, double lat1, double lng1, double lat2, double lng2) { | ||
if (lat1 > lat2) { | ||
double tempLat = lat1, tempLng = lng1; | ||
lat1 = lat2; | ||
lng1 = lng2; | ||
lat2 = tempLat; | ||
lng2 = tempLng; | ||
} | ||
|
||
if (testLat < lat1 || testLat > lat2) { | ||
return false; | ||
} | ||
|
||
if (testLng > Math.max(lng1, lng2)) { | ||
return false; | ||
} | ||
|
||
if (testLng < Math.min(lng1, lng2)) { | ||
return true; | ||
} | ||
|
||
double slope = (lng2 - lng1) / (lat2 - lat1); | ||
double intersectLng = lng1 + (testLat - lat1) * slope; | ||
|
||
return testLng < intersectLng; | ||
} | ||
|
||
/** | ||
* Calculates the distance from a point to the closest boundary of the polygon. | ||
* | ||
* @param polygonPoints A list of doubles representing the polygon vertices | ||
* (latitude and longitude pairs). | ||
* @param testPoint A list of doubles representing the latitude and longitude of the test point. | ||
* @return The distance from the test point to the closest edge of the polygon. | ||
*/ | ||
public static double distanceToClosestBoundary(List<Double> polygonPoints, double[] testPoint) { | ||
double minDistance = Double.MAX_VALUE; | ||
|
||
int vertexCount = polygonPoints.size() / 2; | ||
double testLat = testPoint[0]; | ||
double testLng = testPoint[1]; | ||
|
||
for (int i = 0; i < vertexCount; i++) { | ||
double lat1 = polygonPoints.get(2 * i); | ||
double lng1 = polygonPoints.get(2 * i + 1); | ||
double lat2 = polygonPoints.get((2 * ((i + 1) % vertexCount))); | ||
double lng2 = polygonPoints.get((2 * ((i + 1) % vertexCount)) + 1); | ||
|
||
double distance = pointToSegmentDistance(testLat, testLng, lat1, lng1, lat2, lng2); | ||
minDistance = Math.min(minDistance, distance); | ||
} | ||
|
||
return minDistance; | ||
} | ||
|
||
/** | ||
* Calculates the shortest distance from a point to a line segment. | ||
*/ | ||
private static double pointToSegmentDistance(double px, double py, double x1, double y1, double x2, double y2) { | ||
double dx = x2 - x1; | ||
double dy = y2 - y1; | ||
|
||
if (dx == 0 && dy == 0) { | ||
// The segment is a point | ||
return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2)); | ||
} | ||
|
||
// Calculate the projection of the point onto the line | ||
double t = ((px - x1) * dx + (py - y1) * dy) / (dx * dx + dy * dy); | ||
|
||
if (t < 0) { | ||
// Closest to the first endpoint | ||
return Math.sqrt(Math.pow(px - x1, 2) + Math.pow(py - y1, 2)); | ||
} else if (t > 1) { | ||
// Closest to the second endpoint | ||
return Math.sqrt(Math.pow(px - x2, 2) + Math.pow(py - y2, 2)); | ||
} else { | ||
// Closest to a point on the segment | ||
double projX = x1 + t * dx; | ||
double projY = y1 + t * dy; | ||
return Math.sqrt(Math.pow(px - projX, 2) + Math.pow(py - projY, 2)); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add unit tests for Implementing unit tests for Would you like assistance in creating unit tests for these methods? |
||
} | ||
} |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,56 @@ | ||||||||||||||||||||
package org.javarosa.xpath.expr; | ||||||||||||||||||||
|
||||||||||||||||||||
import org.javarosa.core.model.condition.EvaluationContext; | ||||||||||||||||||||
import org.javarosa.core.model.data.GeoPointData; | ||||||||||||||||||||
import org.javarosa.core.model.data.UncastData; | ||||||||||||||||||||
import org.javarosa.core.model.instance.DataInstance; | ||||||||||||||||||||
import org.javarosa.core.model.utils.PolygonUtils; | ||||||||||||||||||||
import org.javarosa.xpath.XPathTypeMismatchException; | ||||||||||||||||||||
import org.javarosa.xpath.parser.XPathSyntaxException; | ||||||||||||||||||||
|
||||||||||||||||||||
import java.util.ArrayList; | ||||||||||||||||||||
import java.util.List; | ||||||||||||||||||||
|
||||||||||||||||||||
public class XPathBoundaryDistanceFunc extends XPathFuncExpr{ | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we add a javadoc here explaining what this function does. |
||||||||||||||||||||
public static final String NAME = "boundaryDistance"; | ||||||||||||||||||||
private static final int EXPECTED_ARG_COUNT = 2; | ||||||||||||||||||||
|
||||||||||||||||||||
public XPathBoundaryDistanceFunc() { | ||||||||||||||||||||
name = NAME; | ||||||||||||||||||||
expectedArgCount = EXPECTED_ARG_COUNT; | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public XPathBoundaryDistanceFunc(XPathExpression[] args) throws XPathSyntaxException { | ||||||||||||||||||||
super(NAME, args, EXPECTED_ARG_COUNT, true); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
|
||||||||||||||||||||
@Override | ||||||||||||||||||||
protected Object evalBody(DataInstance model, EvaluationContext evalContext, Object[] evaluatedArgs) { | ||||||||||||||||||||
return boundaryDistance(evaluatedArgs[0], evaluatedArgs[1]); | ||||||||||||||||||||
} | ||||||||||||||||||||
|
||||||||||||||||||||
public static Double boundaryDistance(Object from, Object to) { | ||||||||||||||||||||
String unpackedFrom = (String)FunctionUtils.unpack(from); | ||||||||||||||||||||
String unpackedTo = (String)FunctionUtils.unpack(to); | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ensure safe casting from Casting the result of Apply this diff to safely handle the casting: - String unpackedFrom = (String)FunctionUtils.unpack(from);
- String unpackedTo = (String)FunctionUtils.unpack(to);
+ Object unpackedFromObj = FunctionUtils.unpack(from);
+ Object unpackedToObj = FunctionUtils.unpack(to);
+ if (!(unpackedFromObj instanceof String) || !(unpackedToObj instanceof String)) {
+ throw new XPathTypeMismatchException("boundaryDistance() function requires string arguments.");
+ }
+ String unpackedFrom = (String) unpackedFromObj;
+ String unpackedTo = (String) unpackedToObj; 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||
if (unpackedFrom == null || "".equals(unpackedFrom) || unpackedTo == null || "".equals(unpackedTo)) { | ||||||||||||||||||||
return Double.valueOf(-1.0); | ||||||||||||||||||||
} | ||||||||||||||||||||
try { | ||||||||||||||||||||
String[] coordinates=unpackedFrom.split(" "); | ||||||||||||||||||||
List<Double> polygonList = new ArrayList<Double>(); | ||||||||||||||||||||
|
||||||||||||||||||||
for (String coordinate : coordinates) { | ||||||||||||||||||||
polygonList.add(Double.parseDouble(coordinate)); | ||||||||||||||||||||
} | ||||||||||||||||||||
// Casting and uncasting seems strange but is consistent with the codebase | ||||||||||||||||||||
GeoPointData castedTo = new GeoPointData().cast(new UncastData(unpackedTo)); | ||||||||||||||||||||
double distance=PolygonUtils.distanceToClosestBoundary(polygonList,new double[]{castedTo.getLatitude(), castedTo.getLongitude()}); | ||||||||||||||||||||
|
||||||||||||||||||||
return distance; | ||||||||||||||||||||
} catch (NumberFormatException e) { | ||||||||||||||||||||
throw new XPathTypeMismatchException("distance() function requires arguments containing " + | ||||||||||||||||||||
"numeric values only, but received arguments: " + unpackedFrom + " and " + unpackedTo); | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
} | ||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add unit tests for To ensure the correctness and robustness of the Would you like assistance in generating unit tests for this function or opening a GitHub issue to track this task? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
curious if this implementation was taken from somewhere , if so it might be good to reference it in a comment just in case we need to track any future updates to this code.