Determine if point is inside shape SQL Server 2014 [closed] - sql

Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 5 years ago.
Improve this question
I'm developing a GPS project I allow user to draw (using google) a shape(rectangle, polygon, circle, line)
and save it in database
SQL Fiddle
Now my requirement is to determine if a given point within anyone in the table
and return the id else return 0.
the trick is I save multiple shapes (Type column determine the shape type 2 is a rectangle- 4 is polygon)
What I have tried
Example
Note: I will be using this from C# and pass point as parameter

First of all I would suggest you save the Polygons as Spatial Data Types
In JavaScript you can easily convert the drawn polygons to a List/Array of Lat/Lngs
for a circle you need the radius and the center lat/lng of the circle
something like this:
var circleCenter = null;
var circlePolygon = null;
// set draw options on google map
mapObject.setOptions({ draggableCursor: 'crosshair' });
var drawGeofenceEvent = google.maps.event.addListener(mapObject, 'click', event => {
var myLatLng = event.latLng;
// create a marker for center
circleCenter = new google.maps.Marker({
map: mapObject,
position: myLatLng,
draggable: true
});
// create a temp circle polygon to plot on map
circlePolygon = new google.maps.Circle({
radius: 150,
editable: true,
draggable: true
});
circlePolygon.setMap(mapObject);
circlePolygon.bindTo('center', circleCenter, 'position');
google.maps.event.removeListener(drawGeofenceEvent);
});
// after drawing the geofence you want to get the lat/longs along the circumference
var circleCoordinates = this.googleCircleToPolylineArray(this.circleCenter.position, this.circlePolygon.getRadius() * 0.000621371, 1);
// copied from Stackoverflow - will post the link when I find it
googleCircleToPolylineArray(point, radius, dir) {
var lat = point.lat();
var lng = point.lng();
var d2r = Math.PI / 180; // degrees to radians
var r2d = 180 / Math.PI; // radians to degrees
var earthsradius = 3963; // 3963 is the radius of the earth in miles
// ReSharper disable once AssignedValueIsNeverUsed
var points = 32;
//var radius = 1; // radius in miles
// find the raidus in lat/lon
var rlat = (radius / earthsradius) * r2d;
var rlng = rlat / Math.cos(lat * d2r);
var extp = new Array();
for (var i = 0; i < points + 1; i++) {
var theta = Math.PI * (i / (points / 2));
var ex = lng + (rlng * Math.cos(theta)); // center a + radius x * cos(theta)
var ey = lat + (rlat * Math.sin(theta)); // center b + radius y * sin(theta)
extp.push({ Latitude: ey, Longitude: ex }); // new google.maps.LatLng(ey, ex)
}
return extp;
};
// then you would POST the circleCoordinates to the Server to save as a spatial data type
Then on the server you would convert the co-ordinates to a Well-Known-Text (WKT) Wikipedia - for Polygon use the following, when I say Polygon I mean circle, rectangle, triangle, hexagon etc. Anything which has the same start and end point (is closed).
for a line you would use the LINESTRING WKT
public static DbGeography CreatePolygon(Coordinate[] latLongs)
{
//POLYGON ((73.232821 34.191819,73.233755 34.191942,73.233653 34.192358,73.232843 34.192246,73.23269 34.191969,73.232821 34.191819))
var polyString = "";
foreach (var point in latLongs)
{
polyString += point.Longitude + " " + point.Latitude + ",";
}
polyString = polyString.TrimEnd(',');
polyString = string.Format("POLYGON(({0}))", polyString);
var polygonFromText = DbGeography.PolygonFromText(polyString, DbGeography.DefaultCoordinateSystemId);
return polygonFromText;
}
Reverse From DbGeography to Coordinates
public static List<Coordinate> PolygonToGeoPoints(DbGeography sptGeofenceArea)
{
var points = new List<Coordinate>();
string polygonText = sptGeofenceArea.ProviderValue.ToString();
polygonText = polygonText.Replace("POLYGON", "");
polygonText = polygonText.Replace("(", "").Replace(")", "").Trim();
var polPoints = polygonText.Split(',');
foreach (var point in polPoints)
{
var latlong = point.Trim().Split(' ');
points.Add(new Coordinate { Latitude = double.Parse(latlong[1]), Longitude = double.Parse(latlong[0]) });
}
return points;
}
I use Entity Framework and DbGeography types, I save the polygon as a spatial data type in the database.
You can edit the above code to return the Well-Known-Text (WKT) instead of the DbGeography data type.
Then once the spatial data type is stored in the database all you have to do is convert the point you want to check to a spatial data type OR WKT
WKT - SQL VERSION
DECLARE #point GEOGRAPHY;
SET #point = geography::Point(47.653, -122.358, 4326)
Select
*
From Polygons
where POLYGON.STIntersects(#point) = 1
SPATIAL TYPE - ENTITY FRAMEWORK
DbGeography point;
dbCOntext.Polygons.Where(s => point.Intersects(s.Polygon)).ToList();
EDIT
There is a common problem when creating the Polygon types - the points have to be in a certain order otherwise you would have a Polygon covering the entire earth except the required Polygon - to overcome that you can use the following -
LINK
#region
//https://www.exceptionnotfound.net/fixing-sql-server-spatial-not-a-valid-instance-of-geography-errors-in-c-sharp/
private static DbGeography CreatePolygon(string wellKnownText)
{
//First, get the area defined by the well-known text using left-hand rule
var sqlGeography = SqlGeography.STGeomFromText(new SqlChars(wellKnownText), DbGeography.DefaultCoordinateSystemId);
if(!sqlGeography.STIsValid())
throw new Exception("Invalid polygon, please draw the polygon again.");
sqlGeography = sqlGeography.MakeValid();
//Now get the inversion of the above area
var invertedSqlGeography = sqlGeography.ReorientObject();
//Whichever of these is smaller is the enclosed polygon, so we use that one.
if (sqlGeography.STArea() > invertedSqlGeography.STArea())
{
sqlGeography = invertedSqlGeography;
}
return DbSpatialServices.Default.GeographyFromProviderValue(sqlGeography);
}
#endregion
public static DbGeography CreatePolygon(Coordinate[] latLongs)
{
//POLYGON ((73.232821 34.191819,73.233755 34.191942,73.233653 34.192358,73.232843 34.192246,73.23269 34.191969,73.232821 34.191819))
var polyString = "";
foreach (var point in latLongs)
{
polyString += point.Longitude + " " + point.Latitude + ",";
}
polyString = polyString.TrimEnd(',');
polyString = string.Format("POLYGON(({0}))", polyString);
var dbGeographyPolygon = CreatePolygon(polyString);
return dbGeographyPolygon;
//var polygonFromText = DbGeography.PolygonFromText(polyString, DbGeography.DefaultCoordinateSystemId);
//return polygonFromText;
}

Related

What is the optimal render loop in Dart 2?

I am looking for ideas regarding an optimal/minimal structure for the inner render loop in Dart 2, for a 2d game (if that part matters).
Clarification / Explanation: Every framework / language has an efficient way to:
1) Deal with time.
2) Render to the screen (via memory, a canvas, an image, or whatever).
For an example, here is someone that answered this for the C# language. Being new to Flutter / Dart, my first attempt (below), is failing to work and as of right now, I can not tell where the problem is.
I have searched high and low without finding any help on this, so if you can assist, you have my eternal gratitude.
There is a post on Reddit by ‘byu/inu-no-policemen’ (a bit old). I used this to start. I suspect that it is crushing the garbage collector or leaking memory.
This is what I have so far, but it crashes pretty quickly (at least in the debugger):
import 'dart:ui';
import 'dart:typed_data';
import 'dart:math' as math;
import 'dart:async';
main() async {
var deviceTransform = new Float64List(16)
..[0] = 1.0 // window.devicePixelRatio
..[5] = 1.0 // window.devicePixelRatio
..[10] = 1.0
..[15] = 1.0;
var previous = Duration.zero;
var initialSize = await Future<Size>(() {
if (window.physicalSize.isEmpty) {
var completer = Completer<Size>();
window.onMetricsChanged = () {
if (!window.physicalSize.isEmpty) {
completer.complete(window.physicalSize);
}
};
return completer.future;
}
return window.physicalSize;
});
var world = World(initialSize.width / 2, initialSize.height / 2);
window.onBeginFrame = (now) {
// we rebuild the screenRect here since it can change
var screenRect = Rect.fromLTWH(0.0, 0.0, window.physicalSize.width, window.physicalSize.height);
var recorder = PictureRecorder();
var canvas = Canvas(recorder, screenRect);
var delta = previous == Duration.zero ? Duration.zero : now - previous;
previous = now;
var t = delta.inMicroseconds / Duration.microsecondsPerSecond;
world.update(t);
world.render(t, canvas);
var builder = new SceneBuilder()
..pushTransform(deviceTransform)
..addPicture(Offset.zero, recorder.endRecording())
..pop();
window.render(builder.build());
window.scheduleFrame();
};
window.scheduleFrame();
window.onPointerDataPacket = (packet) {
var p = packet.data.first;
world.input(p.physicalX, p.physicalY);
};
}
class World {
static var _objectColor = Paint()..color = Color(0xa0a0a0ff);
static var _s = 200.0;
static var _obejectRect = Rect.fromLTWH(-_s / 2, -_s / 2, _s, _s);
static var _rotationsPerSecond = 0.25;
var _turn = 0.0;
double _x;
double _y;
World(this._x, this._y);
void input(double x, double y) { _x = x; _y = y; }
void update(double t) { _turn += t * _rotationsPerSecond; }
void render(double t, Canvas canvas) {
var tau = math.pi * 2;
canvas.translate(_x, _y);
canvas.rotate(tau * _turn);
canvas.drawRect(_obejectRect, _objectColor);
}
}
Well, after a month of beating my face against this, I finally figured out the right question and that got me to this:
Flutter Layers / Raw
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// This example shows how to perform a simple animation using the raw interface
// to the engine.
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui;
void beginFrame(Duration timeStamp) {
// The timeStamp argument to beginFrame indicates the timing information we
// should use to clock our animations. It's important to use timeStamp rather
// than reading the system time because we want all the parts of the system to
// coordinate the timings of their animations. If each component read the
// system clock independently, the animations that we processed later would be
// slightly ahead of the animations we processed earlier.
// PAINT
final ui.Rect paintBounds = ui.Offset.zero & (ui.window.physicalSize / ui.window.devicePixelRatio);
final ui.PictureRecorder recorder = ui.PictureRecorder();
final ui.Canvas canvas = ui.Canvas(recorder, paintBounds);
canvas.translate(paintBounds.width / 2.0, paintBounds.height / 2.0);
// Here we determine the rotation according to the timeStamp given to us by
// the engine.
final double t = timeStamp.inMicroseconds / Duration.microsecondsPerMillisecond / 1800.0;
canvas.rotate(math.pi * (t % 1.0));
canvas.drawRect(ui.Rect.fromLTRB(-100.0, -100.0, 100.0, 100.0),
ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0));
final ui.Picture picture = recorder.endRecording();
// COMPOSITE
final double devicePixelRatio = ui.window.devicePixelRatio;
final Float64List deviceTransform = Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 1.0;
final ui.SceneBuilder sceneBuilder = ui.SceneBuilder()
..pushTransform(deviceTransform)
..addPicture(ui.Offset.zero, picture)
..pop();
ui.window.render(sceneBuilder.build());
// After rendering the current frame of the animation, we ask the engine to
// schedule another frame. The engine will call beginFrame again when its time
// to produce the next frame.
ui.window.scheduleFrame();
}
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}

How do I add a radius line from the center of the circle to a point on the circle

The following code adds a circle of given radius to the graphics layer on an ArcGIS map. How can I add a line that joins center of the circle to any point on the circle to the graphics layer.
Basically the question is how do I calculate a point on the circle, draw a line that joins the center to the point on the circle and add it to the graphics layer.
performSearchPoint : function(e) {
var self = this;
var radius = $('#radius-distance').val();
if(radius > 0 && radius < 100000){
$('#besideMouse').removeClass('hide');
$('#besideMouse').show();
var loadingBMint = setInterval(this.loadingBM, 0);
var searchPointClick = OURAPP.App.Map.on("click",function(evt) {
loadingBMint = clearInterval(loadingBMint);
$('#besideMouse').hide();
var radius = $('#radius-distance').val();
var units = $("input:radio[name='unitsGroup']:checked").val();
if (units == "miles"){
units = "9035"; // if we use GeometryService
} else {
units = "9003"; // if we use GeometryService
}
//clear only search graphics
for ( var gr in OURAPP.App.Map.graphics.graphics) {
if(OURAPP.App.Map.graphics.graphics[gr].infoTemplate != null){
var template = OURAPP.App.Map.graphics.graphics[gr].infoTemplate;
if(template != "undefined" || template != null){
if(template.title.trim() == "Search Graphic"){
OURAPP.App.Map.graphics.remove(OURAPP.App.Map.graphics.graphics[gr]);
}
}}}
/*do buffer geometry for draw circle and use the circle geometry to get the features*/
var geoService = new OURAPP.esri.GeometryService("http://XXXX:YYYY/arcgis/rest/services/Utilities/Geometry/GeometryServer");
var params = new OURAPP.esri.BufferParameters();
params.geometries = [ evt.mapPoint ];
params.distances = [ radius ];
params.unit = units;
params.bufferSpatialReference = OURAPP.App.Map.spatialReference;
params.outSpatialReference = new OURAPP.esri.SpatialReference(4326);
var bufferPolygon = new OURAPP.esri.Polygon;
bufferPolygon.spatialReference = new OURAPP.esri.SpatialReference(4326);
geoService.buffer(params,function(geometries) {
var symbol = new OURAPP.esri.SimpleFillSymbol()
.setColor(null).outline.setColor("red");
dojo.forEach(geometries,function(geometry) {
geometry.spatialReference = new OURAPP.esri.SpatialReference(4326);
var graphic = new OURAPP.esri.Graphic(geometry,symbol);
// add name to identify the search graphics
var template = new OURAPP.esri.InfoTemplate(graphic.geometry);
template.setTitle("Search Graphic");
template.setContent("Map Query circle with Radius: " + radius);
graphic.setInfoTemplate(template);
OURAPP.App.Map.graphics.add(graphic);
bufferPolygon = geometry;
OURAPP.App.Map.setExtent(graphic.geometry.getExtent().expand(2));
});
self.searchType="Distance Search from point";
self.nameofplace=radius + " "+$("input:radio[name='unitsGroup']:checked").val();
self.showCount(bufferPolygon);
});
searchPointClick.remove();
});
}
},
I was able to draw a line and add it to the graphics layer using the following. The [-XX.XXXXXXXXXXXX,YY.YYYYYYYYYYY] is a random point on the map, Now only thing left is to find a point on a circle. So now the question becomes how to find a point which is X miles from a known point(Center of the circle) along the same latitude.
var lineSymbol = new OURAPP.esri.CartographicLineSymbol(
OURAPP.esri.CartographicLineSymbol.STYLE_SOLID,
new OURAPP.esri.Color([255,0,0]), 2,
OURAPP.esri.CartographicLineSymbol.CAP_SQUARE,
OURAPP.esri.CartographicLineSymbol.JOIN_MITER, 5
);
var lineGeometry = new OURAPP.esri.Polyline;
lineGeometry.spatialReference = new OURAPP.esri.SpatialReference(4326);
lineGeometry.addPath([[evt.mapPoint.getLongitude(),evt.mapPoint.getLatitude()], [-XX.XXXXXXXXXXXX,YY.YYYYYYYYYYY]])
var lineGraphic = new OURAPP.esri.Graphic(lineGeometry, lineSymbol);
OURAPP.App.Map.graphics.add(lineGraphic);
This is the best possible one i came up with and its working.
var lineSymbol = new OURAPP.esri.CartographicLineSymbol(
OURAPP.esri.CartographicLineSymbol.STYLE_SOLID,
new OURAPP.esri.Color([255,0,0]), 2,
OURAPP.esri.CartographicLineSymbol.CAP_SQUARE,
OURAPP.esri.CartographicLineSymbol.JOIN_MITER, 5
);
var radiusInMeters;
if (selectedUnit == "miles"){
radiusInMeters = radius*1609.34; //have to convert it to meters.
} else {
radiusInMeters = radius*0.3048; //have to convert it to meters.
}
// Calculate the new map point on the circle.
var radians = Math.PI/180;
var ltLong = OURAPP.esri.webMercatorUtils.xyToLngLat(evt.mapPoint.x + radiusInMeters*Math.cos(radians), evt.mapPoint.y + radiusInMeters*Math.sin(radians));
// Calculate the new map point on the circle.
var lineGeometry = new OURAPP.esri.Polyline;
lineGeometry.spatialReference = new OURAPP.esri.SpatialReference(4326);
lineGeometry.addPath([[evt.mapPoint.getLongitude(),evt.mapPoint.getLatitude()], ltLong]);
var lineGraphic = new OURAPP.esri.Graphic(lineGeometry, lineSymbol);

Famo.us physics engine: forces and walls not behaving as expected

I am tyring to create a grid of particles, using the famous physics engine. I thought that if 25 particles were placed beteween 4 bounding walls (forming a square) and they all had an equal repulsion force to each other, they would naturally form a grid - ie. they would all be held as far away from each other as possible, given the limits of their world. I expected this to work, even if the particles were just added without an initial position. However, even if I give them an initial position, they don't hold in place unless the force is very small.
Also, I thought that if a wall had a restitution of 0, then a particle would just stop on colliding with it. When I run the code below, I can see particles bouncing off walls. The pen is at:
http://codepen.io/timsig/pen/pJJrOa
What am I failing to grasp? - thanks in advance
define('main', function (require, exports, module) {
// import dependencies
var Engine = require('famous/core/Engine');
var Surface = require('famous/core/Surface')
var Modifier = require('famous/core/Modifier');
var PhysicsEngine = require('famous/physics/PhysicsEngine');
var Particle = require('famous/physics/bodies/Particle');
var Drag = require('famous/physics/forces/Drag');
var RepulsionForce = require('famous/physics/forces/Repulsion');
var Wall = require('famous/physics/constraints/Wall');
var gridItems = [];
var positionsArray = [-140,-70,0,70,140];
var context = Engine.createContext();
var physics = new PhysicsEngine();
var gridR = new RepulsionForce({
strength: 0.015
});
//physics.addBody(planetParticle);
var leftWall = new Wall({normal : [1,0,0], distance : 140, restitution : 0});
var rightWall = new Wall({normal : [-1,0,0], distance : 140, restitution : 0});
var topWall = new Wall({normal : [0,1,0], distance : 140, restitution : 0});
var bottomWall = new Wall({normal : [0,-1,0], distance : 140, restitution : 0});
function gridItemTrans() {
return this.particle.getTransform();
}
function addGridRepulsion(){
var sq1, sq2;
for (var i = 0; i < gridItems.length; i += 1){
sq1 = gridItems[i].particle;
physics.attach([leftWall, rightWall, topWall, bottomWall], sq1);
if ((i + 1) < gridItems.length){
for (var j = i + 1; j < gridItems.length; j += 1){
sq2 = gridItems[j].particle;
physics.attach(gridR, sq1, sq2);
}
}
}
}
function addBodies(){
gridItems.forEach(function(ele){
physics.addBody(ele.particle);
});
}
for (var rows = 0; rows < 5; rows += 1){
for (var cols = 0; cols < 5; cols += 1){
var gridItem = new Surface({
properties: {
backgroundColor: '#23AD23'
}
});
gridItem.particle = new Particle({
position: [positionsArray[rows], positionsArray[cols], 0]
});
//physics.addBody(gridItem.particle);
//physics.attach(centralG, gridItemParticle[rows][cols], planetParticle);
gridItem.modifier = new Modifier({
size: [50,50],
align: [0.5, 0.5],
origin: [0.5, 0.5],
transform: gridItemTrans.bind(gridItem)
});
context.add(gridItem.modifier).add(gridItem);
gridItems.push(gridItem);
}
}
addBodies();
addGridRepulsion();
});
To clear up your understanding, we will fist look at the repulsion being applied.
By Default, the Repulsion decay function in Famo.us is a gravity function (an inverse squared distance decay function). Gravity has a decay based on mass and distance.
var gridR = new RepulsionForce({
strength: 1,
decayFunction : RepulsionForce.DECAY_FUNCTIONS.GRAVITY
});
You can apply a linear function to your repulsion decay and you will create the affect you are looking for.
var gridR = new RepulsionForce({
strength: 1,
decayFunction : RepulsionForce.DECAY_FUNCTIONS.LINEAR
});
If gravity were to decay linearly rather than quadratically, you would need infinite kinetic energy to escape a gravitational field. It would be like living in 2D space.
To answer the second part of your question: The walls have no restitution, but the particles still respond to the force of the other particles.

ThreeJS. How to implement ZoomALL and make sure a given box fills the canvas area?

I am looking for a function to ensure that a given box or sphere will be visible in a WebGL canvas, and that it will fit the canvas area.
I am using a perspective camera and the camera already points to the middle of the object.
I understand that this could be achieved by changing the FOV angle or by moving the camera along the view axis.
Any idea how this could be achieved with ThreeJS ?
This is how I finally implemented it:
var camera = new THREE.PerspectiveCamera(35,1,1, 100000);
var controls = new THREE.TrackballControls( me.camera , container);
[...]
/**
* point the current camera to the center
* of the graphical object (zoom factor is not affected)
*
* the camera is moved in its x,z plane so that the orientation
* is not affected either
*/
function pointCameraTo (node) {
var me = this;
// Refocus camera to the center of the new object
var COG = shapeCenterOfGravity(node);
var v = new THREE.Vector3();
v.subVectors(COG,me.controls.target);
camera.position.addVectors(camera.position,v);
// retrieve camera orientation and pass it to trackball
camera.lookAt(COG);
controls.target.set( COG.x,COG.y,COG.z );
};
/**
* Zoom to object
*/
function zoomObject (node) {
var me = this;
var bbox = boundingBox(node);
if (bbox.empty()) {
return;
}
var COG = bbox.center();
pointCameraTo(node);
var sphereSize = bbox.size().length() * 0.5;
var distToCenter = sphereSize/Math.sin( Math.PI / 180.0 * me.camera.fov * 0.5);
// move the camera backward
var target = controls.target;
var vec = new THREE.Vector3();
vec.subVectors( camera.position, target );
vec.setLength( distToCenter );
camera.position.addVectors( vec , target );
camera.updateProjectionMatrix();
render3D();
};
An example of this can be seen in https://github.com/OpenWebCAD/node-occ-geomview/blob/master/client/geom_view.js
Here is how I did it { Using TrackBall to Zoomin/out pan etc }
function init(){
......
......
controls = new THREE.TrackballControls(camera);
controls.rotateSpeed = 1.0;
controls.zoomSpeed = 1.2;
controls.panSpeed = 0.8;
controls.noZoom = false;
controls.noPan = false;
controls.staticMoving = true;
controls.dynamicDampingFactor = 0.3;
controls.keys = [65, 83, 68];
controls.addEventListener('change', render);
.....
.....
render(scene, camera);
animate();
}
function render()
{
renderer.render(scene, camera);
//stats.update();
}
function animate() {
requestAnimationFrame(animate);
controls.update();
}

I need an algorithm that can fit n rectangles of any size in a larger one minimizing its area

I need an algorithm that would take n rectangles of any sizes, and calculate a rectangle big enough to fit them all, minimizing its area so the wasted area is minimum, and also returning the position of all the smaller rectangles within.
The specific task I need this to implement on is in a sprite sheet compiler that would take individual PNG files and make a large PNG with all the images in it, so individual frames can be blitted from this surface at run time.
A nice to have feature would be that it aims to a specific given width/height ratio, but it's not mandatory.
I'd prefer simple, generic code I can port to another language.
This is what I put together for my own needs. The T parameter is whatever object you want associated with the results (think of it like the Tag property). It takes a list of sizes and returns a list of Rects that are arranged
static class LayoutHelper
{
/// <summary>
/// Determines the best fit of a List of Sizes, into the desired rectangle shape
/// </summary>
/// <typeparam name="T">Holder for an associated object (e.g., window, UserControl, etc.)</typeparam>
/// <param name="desiredWidthToHeightRatio">the target rectangle shape</param>
/// <param name="rectsToArrange">List of sizes that have to fit in the rectangle</param>
/// <param name="lossiness">1 = non-lossy (slow). Greater numbers improve speed, but miss some best fits</param>
/// <returns>list of arranged rects</returns>
static public List<Tuple<T, Rect>> BestFitRects<T>(double desiredWidthToHeightRatio,
List<Tuple<Size, T>> rectsToArrange, int lossiness = 10)
{
// helper anonymous function that tests for rectangle intersections or boundary violations
var CheckIfRectsIntersect = new Func<Rect, List<Rect>, double, bool>((one, list, containerHeight) =>
{
if (one.Y + one.Height > containerHeight) return true;
return list.Any(two =>
{
if ((one.Top > two.Bottom) ||
(one.Bottom < two.Top) ||
(one.Left > two.Right) ||
(one.Right < two.Left)) return false; // no intersection
return true; // intersection found
});
});
// helper anonymous function for adding drop points
var AddNewPotentialDropPoints = new Action<SortedDictionary<Point, object>, Rect>(
(potentialDropPoints, newRect) =>
{
// Only two locations make sense for placing a new rectangle, underneath the
// bottom left corner or to the right of a top right corner
potentialDropPoints[new Point(newRect.X + newRect.Width + 1,
newRect.Y)] = null;
potentialDropPoints[new Point(newRect.X,
newRect.Y + newRect.Height + 1)] = null;
});
var sync = new object();
// the outer boundary that limits how high the rectangles can stack vertically
var containingRectHeight = Convert.ToInt32(rectsToArrange.Max(a => a.Item1.Height));
// always try packing using the tallest rectangle first, working down in height
var largestToSmallest = rectsToArrange.OrderByDescending(a => a.Item1.Height).ToList();
// find the maximum possible container height needed
var totalHeight = Convert.ToInt32(rectsToArrange.Sum(a => a.Item1.Height));
List<Tuple<T, Rect>> bestResults = null;
// used to find the best packing arrangement that approximates the target container dimensions ratio
var bestResultsProximityToDesiredRatio = double.MaxValue;
// try all arrangements for all suitable container sizes
Parallel.For(0, ((totalHeight + 1) - containingRectHeight) / lossiness,
//new ParallelOptions() { MaxDegreeOfParallelism = 1},
currentHeight =>
{
var potentialDropPoints = new SortedDictionary<Point, object>(Comparer<Point>.Create((p1, p2) =>
{
// choose the leftmost, then highest point as earlier in the sort order
if (p1.X != p2.X) return p1.X.CompareTo(p2.X);
return p1.Y.CompareTo(p2.Y);
}));
var localResults = new List<Tuple<T, Rect>>();
// iterate through the rectangles from largest to smallest
largestToSmallest.ForEach(currentSize =>
{
// check to see if the next rectangle fits in with the currently arranged rectangles
if (!potentialDropPoints.Any(dropPoint =>
{
var workingPoint = dropPoint.Key;
Rect? lastFittingRect = null;
var lowY = workingPoint.Y;
var highY = workingPoint.Y - 1;
var boundaryFound = false;
// check if it fits in the current arrangement of rects
do
{
// create a positioned rectangle out of the size dimensions
var workingRect = new Rect(workingPoint,
new Point(workingPoint.X + currentSize.Item1.Width,
workingPoint.Y + currentSize.Item1.Height));
// keep moving it up in binary search fashion until it bumps the higher rect
if (!CheckIfRectsIntersect(workingRect, localResults.Select(a => a.Item2).ToList(),
containingRectHeight + (currentHeight * lossiness)))
{
lastFittingRect = workingRect;
if (!boundaryFound)
{
highY = Math.Max(lowY - ((lowY - highY) * 2), 0);
if (highY == 0) boundaryFound = true;
}
else
{
lowY = workingPoint.Y;
}
}
else
{
boundaryFound = true;
highY = workingPoint.Y;
}
workingPoint = new Point(workingPoint.X, lowY - (lowY - highY) / 2);
} while (lowY - highY > 1);
if (lastFittingRect.HasValue) // found the sweet spot for this rect
{
var newRect = lastFittingRect.Value;
potentialDropPoints.Remove(dropPoint.Key);
// successfully found the best location for the new rectangle, so add it to the pending results
localResults.Add(Tuple.Create(currentSize.Item2, newRect));
AddNewPotentialDropPoints(potentialDropPoints, newRect);
return true;
}
return false;
}))
{
// this only occurs on the first square
var newRect = new Rect(0, 0, currentSize.Item1.Width, currentSize.Item1.Height);
localResults.Add(Tuple.Create(currentSize.Item2, newRect));
AddNewPotentialDropPoints(potentialDropPoints, newRect);
}
});
// layout is complete, now see if this layout is the best one found so far
var layoutHeight = localResults.Max(a => a.Item2.Y + a.Item2.Height);
var layoutWidth = localResults.Max(a => a.Item2.X + a.Item2.Width);
var widthMatchingDesiredRatio = desiredWidthToHeightRatio * layoutHeight;
double ratioProximity;
if (layoutWidth < widthMatchingDesiredRatio)
ratioProximity = widthMatchingDesiredRatio / layoutWidth;
else
ratioProximity = layoutWidth / widthMatchingDesiredRatio;
lock (sync)
{
if (ratioProximity < bestResultsProximityToDesiredRatio)
{
// this layout is the best approximation of the desired container dimensions, so far
bestResults = localResults;
bestResultsProximityToDesiredRatio = ratioProximity;
}
}
});
return bestResults ?? new List<Tuple<T, Rect>>() {Tuple.Create(rectsToArrange[0].Item2,
new Rect(new Point(0, 0), new Point(rectsToArrange[0].Item1.Width, rectsToArrange[0].Item1.Height))) };
}
}