Tuve que escribir un algoritmo para fusionar polígonos adyacentes como parte de un proyecto de experimento con lienzo HTML5 (nada glorioso, un rompecabezas :-) Los agujeros en el polígono resultante son naturalmente compatibles. La rutina Javascript se encuentra en la función llamada Polygon.prototype.merge() en www punto raymondhill punto net/rompecabezas-rhill/jigsawpuzzle-rhill-3 punto js
La clave es eliminar los segmentos que están duplicados, pero de direccion opuesta. Explicación aproximada: un punto es {x:?, Y :?}, un segmento es {ptA:?, PtB :?}, un contorno es {pts: []} (una colección de objetos Point conectados), un polígono es {contornos: []} (una colección de objetos Contour.)
El algoritmo de combinación recopila todos los segmentos en un gran grupo de Objetos de segmento, donde se eliminan los duplicados. Primero, todos los segmentos de todos los contornos que definen el Polígono A se agregan al grupo. Luego, todos los segmentos de todos los contornos que definen el Polígono B se agregan al grupo, pero lo probamos y eliminamos por duplicado (lo hacemos fácilmente usando un objeto Punto como una clave hash).
Luego tomamos un segmento del grupo (al azar está bien), y lo "caminamos" hasta que llegamos a un "callejón sin salida", es decir, no se puede conectar más segmentos. Esto forma un objeto Contour. Repetimos hasta que se haya utilizado toda la colección de segmentos. A medida que se utilizan segmentos, se eliminan del grupo. "Caminar" un segmento significa que tomamos su punto final, y buscamos un segmento cuyo punto de inicio coincida con él.
Como dije, como resultado tenemos una colección de objetos Contour que definen el Polígono. Algunos contornos se llenarán, algunos pueden ser huecos. Determinar si un contorno está lleno o hueco es solo una cuestión de probar si el contorno es en sentido horario o antihorario, o si su área es positiva o negativa. Es una convención, en mi caso los contornos en el sentido de las agujas del reloj están llenos, en sentido antihorario son huecos.
Aquí está mi implementación, menos los detalles y el manejo de errores menos. Con suerte he copiado/pegado suficiente para que pueda hacer que funcione de inmediato, de lo contrario se refiere a mi archivo JS encima de contexto:
// Point object
function Point(a,b) {
// a=x,b=y
if (b) {
this.x=a;
this.y=b;
}
// a=Point or {x:?,y:?}
else if (a) {
this.x=a.x;
this.y=a.y;
}
// empty
else {
this.x=this.y=0;
}
}
Point.prototype.toHashkey = function() {
return this.x+"_"+this.y;
};
Point.prototype.clone = function() {
return new Point(this);
};
// Segment object
function Segment(a,b) {
this.ptA = new Point(a);
this.ptB = new Point(b);
}
// Contour object
function Contour(a) {
this.pts = []; // no points
if (a) {
if (a instanceof Array) { // assume array of Point objects
var nPts = a.length;
for (var iPt=0; iPt<nPts; iPt++) {
this.pts.push(a[iPt].clone());
}
}
}
}
Contour.prototype.clone = function() {
return new Contour(this);
};
Contour.prototype.addPoint = function(p) {
this.pts.push(p);
};
// Polygon object
function Polygon(a) {
this.contours = []; // no contour
if (a) {
if (a instanceof Polygon) {
var contours = a.contours;
var nContours = contours.length;
for (var iContour=0; iContour<nContours; iContour++) {
this.contours.push(new Contour(contours[iContour]));
}
}
else if (a instanceof Array) {
this.contours.push(new Contour(a));
}
}
}
Polygon.prototype.merge = function(other) {
// A Javascript object can be used as an associative array, but
// they are not real associative array, as there is no way
// to query the number of entries in the object. For this
// reason, we maintain an element counter ourself.
var segments={};
var contours=this.contours;
var nContours=contours.length;
var pts; var nPts;
var iPtA; var iPtB;
var idA; var idB;
for (var iContour=0; iContour<nContours; iContour++) {
pts = contours[iContour].pts;
nPts = pts.length;
iPtA = nPts-1;
for (iPtB=0; iPtB<nPts; iPtA=iPtB++) {
idA = pts[iPtA].toHashkey();
idB = pts[iPtB].toHashkey();
if (!segments[idA]) {
segments[idA]={n:0,pts:{}};
}
segments[idA].pts[idB] = new Segment(pts[iPtA],pts[iPtB]);
segments[idA].n++;
}
}
// enumerate segments in other's contours, eliminate duplicate
contours = other.contours;
nContours = contours.length;
for (iContour=0; iContour<nContours; iContour++) {
pts = contours[iContour].pts;
nPts = pts.length;
iPtA=nPts-1;
for (iPtB=0; iPtB<nPts; iPtA=iPtB++) {
idA = pts[iPtA].toHashkey();
idB = pts[iPtB].toHashkey();
// duplicate (we eliminate same segment in reverse direction)
if (segments[idB] && segments[idB].pts[idA]) {
delete segments[idB].pts[idA];
if (!--segments[idB].n) {
delete segments[idB];
}
}
// not a duplicate
else {
if (!segments[idA]) {
segments[idA]={n:0,pts:{}};
}
segments[idA].pts[idB] = new Segment(pts[iPtA],pts[iPtB]);
segments[idA].n++;
}
}
}
// recreate and store new contours by jumping from one point to the next,
// using the second point of the segment as hash key for next segment
this.contours=[]; // regenerate new contours
var contour;
for (idA in segments) { // we need this to get a starting point for a new contour
contour = new Contour();
this.contours.push(contour);
for (idB in segments[idA].pts) {break;}
segment = segments[idA].pts[idB];
while (segment) {
contour.addPoint(new Point(segment.ptA));
// remove from collection since it has now been used
delete segments[idA].pts[idB];
if (!--segments[idA].n) {
delete segments[idA];
}
idA = segment.ptB.toHashkey();
if (segments[idA]) {
for (idB in segments[idA].pts) {break;} // any end point will do
segment = segments[idA].pts[idB];
}
else {
segment = null;
}
}
}
};
Cuando "caminar" el segmento para crear un contorno, hay un caso en que una segmento puede conectarse a más de un segmento:
+------+-------+
| Poly A | two segments sharing same start point Z
| |
+ +---<---Z---->---+
| | | Poly B |
| | | |
+ +-------+--------+
| |
| |
+------+-------+--------+
que puede conducir a dos resultados válidos (el algoritmo anterior dará lugar al azar a uno o el otro):
resultado 1, un contorno llenado:
+------+--->---+
| Poly A |
| |
+ +---<---+---->---+
| | | |
| | | |
+ +--->---+ +
| |
| |
+------+---<---+--------+
Resultado 2, un contorno de llenado, un contorno hueco:
+------+--->---+
| Poly A |
| |
+ +---<---+---->---+
| | Hole A| |
| | | |
+ +--->---+ +
| |
| |
+------+---<---+--------+
Esto no debería ser un problema, aunque, como su código ya debería estar listo para manejar agujeros.
Otro detalle importante: El algoritmo anterior no se deshace de puntos ('+') intermedios, de hecho, se espera o de lo contrario el algoritmo no funcionarán, como en el siguiente caso:
+------>-------+
| Poly A |
| |
| | +---->---+
| | | Poly B |
| | | |
| | +----<---+
| |
| |
+------<-------+
Según tengo entendido, esto es lo que tienes. Me imagino que el algoritmo puede ser extendida para soportar este caso mediante la búsqueda y la adición de los puntos de intersección de antemano (no era necesario en mi caso):
+------>-------+
| Poly A |
| |
| + +---->---+
| | | Poly B |
| | | |
| + +----<---+
| |
| |
+------<-------+
Esperanza esta ayuda.
P.S .: Usted puede 'probar' el algoritmo con el rompecabezas, encajando piezas juntas para generar agujeros, etc .: http://www.raymondhill.net/puzzle-rhill/puzzle-rhill.php?puzzlePieces=16&puzzleComplexity=1&puzzleURL=http://www.public-domain-photos.com/free-stock-photos-4/travel/los-angeles/los-angeles-skyline.jpg&puzzleRotate=0&puzzleVersion=3
¿Lenguaje de programación? –
El perímetro de cualquier mancha de rectángulos adyacentes forma un polígono. ¿Tu pregunta es "¿Cómo hago una lista de los segmentos de línea que definen el perímetro de un conjunto de rectángulos conectados?" ¿o algo mas? – mbeckish
Cuando dices "muchos son adyacentes entre sí", ¿qué significa eso? ¿Simplemente tocan en los bordes o pueden superponerse los rectángulos? ¿Están los rectángulos en una cuadrícula de algún tipo, o pueden sus vértices estar en alguna parte? –