Skip to content
Merged
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
57 changes: 57 additions & 0 deletions ResPlan.Library/Models.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.Json.Serialization;
using MessagePack;
using NetTopologySuite.Geometries;
using NetTopologySuite.Geometries.Utilities;
using ResPlan.Library.Data;

namespace ResPlan.Library
Expand Down Expand Up @@ -51,6 +53,61 @@ public class Plan

[Key(3)]
public Graph ReferenceGraph { get; set; }

public void Rotate(double angleRadians, Coordinate center)
{
var transform = new AffineTransformation();
transform.Rotate(angleRadians, center.X, center.Y);

ApplyTransformation(transform);
}

public void Translate(double dx, double dy)
{
var transform = new AffineTransformation();
transform.Translate(dx, dy);

ApplyTransformation(transform);
}

private void ApplyTransformation(AffineTransformation transform)
{
// Transform geometries
foreach (var key in Geometries.Keys.ToList())
{
var originalList = Geometries[key];
var newList = new List<Geometry>();
foreach (var geom in originalList)
{
var newGeom = transform.Transform(geom);
newList.Add(newGeom);
}
Geometries[key] = newList;
}

// Update Bounds
var newEnvelope = new Envelope();
foreach (var list in Geometries.Values)
{
foreach (var geom in list)
{
newEnvelope.ExpandToInclude(geom.EnvelopeInternal);
}
}
Bounds = newEnvelope;

// Transform Graph Nodes
if (ReferenceGraph != null && ReferenceGraph.Nodes != null)
{
foreach (var node in ReferenceGraph.Nodes.Values)
{
if (node.Geometry != null)
{
node.Geometry = transform.Transform(node.Geometry);
}
}
}
}
}

[MessagePackObject]
Expand Down
13 changes: 13 additions & 0 deletions ResPlan.Library/PlanGenerationConstraints.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Numerics;
using NetTopologySuite.Geometries;

namespace ResPlan.Library
{
public class PlanGenerationConstraints
{
public Polygon BoundingPolygon { get; set; }
public Vector2? FrontDoorFacing { get; set; }
public Vector2? GarageFacing { get; set; }
}
}
81 changes: 78 additions & 3 deletions ResPlan.Library/PlanLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,14 @@
{
private static readonly WKTReader _wktReader = new WKTReader();

public static async Task<List<Plan>> LoadPlansAsync(string jsonPath = null, string pklPathOverride = null, int? maxItems = null, Action<string> logger = null)
public static async Task<List<Plan>> LoadPlansAsync(string jsonPath = null, string pklPathOverride = null, int? maxItems = null, Action<string> logger = null, PlanGenerationConstraints constraints = null)

Check warning on line 19 in ResPlan.Library/PlanLoader.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 19 in ResPlan.Library/PlanLoader.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 19 in ResPlan.Library/PlanLoader.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.

Check warning on line 19 in ResPlan.Library/PlanLoader.cs

View workflow job for this annotation

GitHub Actions / build

Cannot convert null literal to non-nullable reference type.
{
if (!string.IsNullOrEmpty(jsonPath) && File.Exists(jsonPath))
{
return LoadPlansFromJson(jsonPath);
// In this path, we don't apply constraints inside LoadPlansFromJson for now, or we should?
// The prompt says "When using the API". It's better to be consistent.
var loaded = LoadPlansFromJson(jsonPath);
return ApplyConstraints(loaded, constraints, logger);
}

// Python Loading Path (Subprocess approach due to Python.NET threading issues in some envs)
Expand Down Expand Up @@ -115,7 +118,79 @@
}
}

return plans;
return ApplyConstraints(plans, constraints, actualLogger);
}

private static List<Plan> ApplyConstraints(List<Plan> plans, PlanGenerationConstraints constraints, Action<string> logger)
{
if (constraints == null) return plans;

if (constraints.GarageFacing.HasValue)
{
logger?.Invoke("Warning: Garage/Driveway facing constraints are not supported by the current ResPlan dataset.");
}

var result = new List<Plan>();
foreach (var plan in plans)
{
// Apply Facing Constraint
if (constraints.FrontDoorFacing.HasValue)
{
// Find front door
Geometry frontDoor = null;
if (plan.Geometries.ContainsKey("front_door"))
{
var fds = plan.Geometries["front_door"];
if (fds.Count > 0)
{
// If multiple, pick first?
frontDoor = fds[0];
}
}

if (frontDoor != null)
{
var centroid = frontDoor.Centroid.Coordinate;
var planCenter = plan.Bounds.Centre; // Envelope center

// Vector from plan center to door center
var currentVec = new NetTopologySuite.Geometries.Coordinate(centroid.X - planCenter.X, centroid.Y - planCenter.Y);

// If center and door are same (rare), skip
if (Math.Abs(currentVec.X) > 1e-6 || Math.Abs(currentVec.Y) > 1e-6)
{
var targetX = constraints.FrontDoorFacing.Value.X;
var targetY = constraints.FrontDoorFacing.Value.Y;

// Angle of current vector
var currentAngle = Math.Atan2(currentVec.Y, currentVec.X);
// Angle of target
var targetAngle = Math.Atan2(targetY, targetX);

var diff = targetAngle - currentAngle;

plan.Rotate(diff, planCenter);
}
}
}

// Apply Bounding Constraint
if (constraints.BoundingPolygon != null)
{
// Check if plan bounds are within polygon
// Since plan.Bounds is an Envelope, we convert to geometry
var geometryFactory = new GeometryFactory();
var planGeom = geometryFactory.ToGeometry(plan.Bounds);

if (!constraints.BoundingPolygon.Contains(planGeom))
{
continue; // Filter out
}
}

result.Add(plan);
}
return result;
}

private static Plan ConvertDataToPlan(ResPlanData data)
Expand Down
Loading