Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions Zero_engine.alpx
Original file line number Diff line number Diff line change
Expand Up @@ -1354,11 +1354,6 @@ LARGE_CONNECTION (grootverbruik, > 3x80)
<Id>1753342021373</Id>
<Name><![CDATA[SerialisationMixins]]></Name>
</Folder>
<Folder>
<Id>1764938574089</Id>
<Name><![CDATA[Globals]]></Name>
<ParentId>1752680962144</ParentId>
</Folder>
<Folder>
<Id>1772100250031</Id>
<Name><![CDATA[ManagementClasses]]></Name>
Expand Down Expand Up @@ -1443,6 +1438,10 @@ LARGE_CONNECTION (grootverbruik, > 3x80)
<Name><![CDATA[FlexProfileManagement]]></Name>
<ParentId>1772100318398</ParentId>
</Folder>
<Folder>
<Id>1783069714235</Id>
<Name><![CDATA[Utils]]></Name>
</Folder>
</Folders>
<ActiveObjectClasses/>
<DifferentialEquationsMethod>EULER</DifferentialEquationsMethod>
Expand Down Expand Up @@ -2097,7 +2096,7 @@ LARGE_CONNECTION (grootverbruik, > 3x80)
<JavaClass>
<Id>1770210283060</Id>
<Name><![CDATA[DoubleCompare]]></Name>
<Folder>1764938574089</Folder>
<Folder>1783069714235</Folder>
</JavaClass>
<JavaClass>
<Id>1770648718773</Id>
Expand Down Expand Up @@ -2251,6 +2250,7 @@ LARGE_CONNECTION (grootverbruik, > 3x80)
<JavaClass>
<Id>1781523461253</Id>
<Name><![CDATA[LUXMath]]></Name>
<Folder>1783069714235</Folder>
</JavaClass>
<JavaClass>
<Id>1781622084102</Id>
Expand All @@ -2265,11 +2265,17 @@ LARGE_CONNECTION (grootverbruik, > 3x80)
<JavaClass>
<Id>1781710977201</Id>
<Name><![CDATA[GISUtil]]></Name>
<Folder>1783069714235</Folder>
</JavaClass>
<JavaClass>
<Id>1782137734965</Id>
<Name><![CDATA[DataSetConstructor]]></Name>
<Folder>1764938574089</Folder>
<Folder>1783069714235</Folder>
</JavaClass>
<JavaClass>
<Id>1783014017243</Id>
<Name><![CDATA[UIUtil]]></Name>
<Folder>1783069714235</Folder>
</JavaClass>
</JavaClasses>
<RequiredLibraryReference>
Expand Down
131 changes: 131 additions & 0 deletions _alp/Classes/Class.GISUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,141 @@
* GISUtil
*/
public class GISUtil {

public static double calculateManhattanDistance_m(double latitude1, double longitude1, double latitude2, double longitude2) {
double latDist_m = getDistanceGIS(latitude1, longitude1, latitude2, longitude1); //Use anylogic getDistanceGIS function for point 1
double longDist_m = getDistanceGIS(latitude2, longitude1, latitude2, longitude2); //Use anylogic getDistanceGIS function
double totalDist_m = latDist_m + longDist_m;
return totalDist_m;
}

public static double[] calculateSquareCoordinates(double lat, double lon, double area_m2) {
/**
* Computes a list of double[] coordinates representing a perfectly square polygon centered
* at (lat, lon) with a physical area of 'area_m2' in square meters, correcting for local latitude "deformation".
*/

double side_m = Math.sqrt(area_m2);
double halfSide_m = side_m / 2.0;

// Earth conversion factors (approximate for localized areas)
double metersPerDegLat = 111320.0;
double metersPerDegLon = 111320.0 * Math.cos(Math.toRadians(lat));

double offsetLat = halfSide_m / metersPerDegLat;
double offsetLon = halfSide_m / metersPerDegLon;

return new double[]{
lat + offsetLat, lon - offsetLon, // Top-left
lat + offsetLat, lon + offsetLon, // Top-right
lat - offsetLat, lon + offsetLon, // Bottom-right
lat - offsetLat, lon - offsetLon // Bottom-left
};
}

public static double[] calculateCircleCoordinates(double lat, double lon, double area_m2) {
/**
* Computes a list of double[] coordinates representing a perfectly circular polygon centered
* at (lat, lon) with a physical area of 'area_m2' in square meters, correcting for local latitude "deformation".
*/

double radius_m = Math.sqrt(area_m2 / Math.PI); // A circle's radius in meters from its area in m²: Area = pi * r^2 => r = sqrt(Area / pi)

int numPoints = 48; // Number of points (vertices) to approximate the circle
double[] polyCoords = new double[numPoints * 2];

// Earth conversion factors (approximate for localized areas)
double metersPerDegLat = 111320.0;
double metersPerDegLon = 111320.0 * Math.cos(Math.toRadians(lat));

for (int i = 0; i < numPoints; i++) {
double angle = 2.0 * Math.PI * i / numPoints;
double offsetLat = (radius_m * Math.sin(angle)) / metersPerDegLat;
double offsetLon = (radius_m * Math.cos(angle)) / metersPerDegLon;

polyCoords[2 * i] = lat + offsetLat;
polyCoords[2 * i + 1] = lon + offsetLon;
}

return polyCoords;
}

public static double[] calculateCustomPolygonCoordinates(List<Point> coordinateList) {
/**
* Computes a list of double[] coordinates from a list of coordinate Points.
*/

int size = coordinateList.size();
double[] polyCoords = new double[size * 2];
for (int i = 0; i < size; i++) {
Point p = coordinateList.get(i);
polyCoords[2 * i] = p.getX();
polyCoords[2 * i + 1] = p.getY();
}
return polyCoords;
}

public static boolean arePolygonEdgesSelfIntersecting(List<Point> vertices) {
/**
* Checks if a polygon represented by a list of Point vertices is self-intersecting.
* Returns true if any two non-adjacent edges intersect.
*/

int n = vertices.size();
if (n < 4) {
return false; // A polygon with less than 4 vertices cannot self-intersect
}
for (int i = 0; i < n; i++) {
Point p1 = vertices.get(i);
Point q1 = vertices.get((i + 1) % n);
for (int j = i + 2; j < n; j++) {
// Skip adjacent edges (they naturally share a vertex)
if (i == 0 && j == n - 1) {
continue;
}
Point p2 = vertices.get(j);
Point q2 = vertices.get((j + 1) % n);
if (areSegmentsIntersecting(p1.getX(), p1.getY(), q1.getX(), q1.getY(), p2.getX(), p2.getY(), q2.getX(), q2.getY())) {
return true; // Intersection detected
}
}
}
return false;

}

public static boolean areSegmentsIntersecting(double p1x, double p1y, double q1x, double q1y,
double p2x, double p2y, double q2x, double q2y) {
/**
* Checks if line segment p1q1 and p2q2 intersect.
*/

int o1 = getOrientation(p1x, p1y, q1x, q1y, p2x, p2y);
int o2 = getOrientation(p1x, p1y, q1x, q1y, q2x, q2y);
int o3 = getOrientation(p2x, p2y, q2x, q2y, p1x, p1y);
int o4 = getOrientation(p2x, p2y, q2x, q2y, q1x, q1y);
// General Case: Segments cross each other
if (o1 != o2 && o3 != o4) return true;
// Special Cases (Collinear segments overlapping)
if (o1 == 0 && isPointOnSegment(p1x, p1y, p2x, p2y, q1x, q1y)) return true;
if (o2 == 0 && isPointOnSegment(p1x, p1y, q2x, q2y, q1x, q1y)) return true;
if (o3 == 0 && isPointOnSegment(p2x, p2y, p1x, p1y, q2x, q2y)) return true;
if (o4 == 0 && isPointOnSegment(p2x, p2y, q1x, q1y, q2x, q2y)) return true;
return false;
}

// Helper: Checks if point q lies on line segment pr
private static boolean isPointOnSegment(double px, double py, double qx, double qy, double rx, double ry) {
return qx <= max(px, rx) && qx >= min(px, rx) &&
qy <= max(py, ry) && qy >= min(py, ry);
}

// Helper: Finds the orientation of ordered triplet (p, q, r).
// Returns: 0 -> Collinear, 1 -> Clockwise, 2 -> Counterclockwise
private static int getOrientation(double px, double py, double qx, double qy, double rx, double ry) {
double val = (qy - py) * (rx - qx) - (qx - px) * (ry - qy);
if (DoubleCompare.equalsZero(val)) return 0;
return (val > 0) ? 1 : 2;
}

}
113 changes: 113 additions & 0 deletions _alp/Classes/Class.UIUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/**
* UIUtil
*
* Notes/assumptions:
* - Shapes are assumed to be unrotated and in the same coordinate space
* (i.e. same parent group / level).
* - The text shape's Y is treated as the top of the text block, matching
* AnyLogic's text shape anchor.
*/

import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;

public class UIUtil {

/**
* Standalone render context for text measurement. Arguments:
* 1. transform (null) - no scaling/rotation of the target surface; measure in plain user-space pixels (identity)
* 2. isAntiAliased (true) - assume glyph edges are smoothed, matching how AnyLogic renders presentation text
* 3. usesFractionalMetrics (true) - keep exact fractional glyph widths instead of rounding to whole pixels, so measured size scales linearly with font size
*/
private static final FontRenderContext FRC = new FontRenderContext(null, true, true);

public static void fitTextInRectangleVertical(ShapeText textShape, ShapeRectangle rectShape, double topMargin_px, double bottomMargin_px) {
fitTextInRectangle(textShape, rectShape, topMargin_px, bottomMargin_px, 0.0, 0.0);
}

public static void fitTextInRectangleHorizontal(ShapeText textShape, ShapeRectangle rectShape, double leftMargin_px, double rightMargin_px) {
fitTextInRectangle(textShape, rectShape, 0.0, 0.0, leftMargin_px, rightMargin_px);
}

public static void fitTextInRectangle(ShapeText textShape, ShapeRectangle rectShape, double margin_px) {
fitTextInRectangle(textShape, rectShape, margin_px, margin_px, margin_px, margin_px);
}

public static void fitTextInRectangle(ShapeText textShape, ShapeRectangle rectShape, double topMargin_px, double bottomMargin_px, double leftMargin_px, double rightMargin_px) {
/**
* Scales the font of textShape (up or down) so the text block fits exactly
* within rectShape minus the given margins, and centers the block inside
* that margin box. The binding axis (width or height) touches its margins
* exactly; the other axis gets the remaining space distributed equally.
*/
if (textShape == null || rectShape == null || textShape.getText() == null || textShape.getText().isEmpty() || textShape.getFont() == null) {
return;
}

double availableWidth = rectShape.getWidth() - leftMargin_px - rightMargin_px;
double availableHeight = rectShape.getHeight() - topMargin_px - bottomMargin_px;
if (availableWidth <= 0 || availableHeight <= 0) {
return; // margins consume the whole rectangle
}

Font font = textShape.getFont();
String[] lines = textShape.getText().split("\n", -1); // Limit -1 keeps trailing empty lines, so they still count for height

double maxLineWidth = maxLineWidth(font, lines);
double textHeight = lines.length * lineHeight(font);
if (maxLineWidth <= 0 || textHeight <= 0) {
return; // text contains no measurable glyphs
}

// One scale factor for both axes; the smaller ratio is the binding axis
double scale = Math.min(availableWidth / maxLineWidth, availableHeight / textHeight);
float newSize = (float) (font.getSize2D() * scale);
Font fittedFont = font.deriveFont(newSize);

// Font metrics are not perfectly linear in size (hinting, rounding): step down until the fit is guaranteed
while (maxLineWidth(fittedFont, lines) > availableWidth || lines.length * lineHeight(fittedFont) > availableHeight) {
newSize = newSize - 0.5f;
fittedFont = fittedFont.deriveFont(newSize);
}
textShape.setFont(fittedFont);

// Center the fitted block inside the margin box
double fittedWidth = maxLineWidth(fittedFont, lines);
double fittedHeight = lines.length * lineHeight(fittedFont);
double blockLeft = rectShape.getX() + leftMargin_px + 0.5 * (availableWidth - fittedWidth);
double blockTop = rectShape.getY() + topMargin_px + 0.5 * (availableHeight - fittedHeight);

textShape.setY(blockTop);
textShape.setX(blockLeft + alignmentAnchorOffset(textShape, fittedWidth));
}

public static double maxLineWidth(Font font, String[] lines) {
/** Width of the widest line of text. */
double max = 0.0;
for (String line : lines) {
if (line.isEmpty()) continue;
Rectangle2D bounds = font.getStringBounds(line, FRC);
if (bounds.getWidth() > max) {
max = bounds.getWidth();
}
}
return max;
}

public static double lineHeight(Font font) {
/** Height of a single line (ascent + descent + leading) for this font. */
return font.getLineMetrics("Ag", FRC).getHeight();
}

public static double alignmentAnchorOffset(ShapeText textShape, double textWidth) {
/**
* AnyLogic anchors a text shape's X at its alignment point: left edge for
* left-aligned, center for center-aligned, right edge for right-aligned.
*/
TextAlignment alignment = textShape.getAlignment();
if (alignment == TextAlignment.ALIGNMENT_CENTER) return 0.5 * textWidth;
if (alignment == TextAlignment.ALIGNMENT_RIGHT) return textWidth;
return 0.0; // ALIGNMENT_LEFT or null
}

}