Apakah ada algoritma standar / yang direkomendasikan untuk menyederhanakan poligon tanpa mengecilkan batas aslinya?
Saat ini saya menggunakan TopologyPreservingSimplifer dalam JTS dan mengalami masalah di aplikasi saya ketika saya menemukan poligon "lossy". Idealnya, saya ingin memproduksi poligon yang disederhanakan yang lebih kecil dari lambung cembung tetapi tetap menjadi superset dari poligon asli saya.
Memperbarui:
Saya akhirnya menemukan algoritma yang diakui tidak sempurna yang menempatkan "wrapper" di sekitar poligon input, menyusutnya hingga tidak ada area berlebih yang melebihi persentase dari total area input, kemudian menjalankan penyederhanaan garis dengan ambang batas yang jauh lebih halus untuk dilepas setiap titik redundan sepanjang garis lurus. 100% tergantung data, tapi saya melihat 80% kompresi vertex dengan area kelebihan minimal. Semua umpan balik / komentar dihargai:
public class LosslessPolygonSimplifier {
protected final static Logger logger = Logger.getLogger(LosslessPolygonSimplifier.class.getName());
public static Polygon simplify(Polygon input) {
final double AREA_THRESHOLD = 0.005; // allow excesses up to half a percent of total original area
final double LINE_THRESHOLD = 0.0001; // fine threshold to strip straight lines
try {
if (!input.isSimple()) {
logger.warning("Attempting to simplify complex polygon!");
}
Polygon simple = simplifyInternal(input, AREA_THRESHOLD, LINE_THRESHOLD);
return simple;
}
catch (Exception e) {
logger.log(Level.WARNING, "Failed to simplify. Resorting to convex hull.\n " + input.toText(), e);
try {
// worst case scenario - fall back to convex hull
// probably a result of a bow-tie LINESTRING that doubles back on itself due to precision loss?
return (Polygon) input.convexHull();
}
catch (Exception e2) {
// Is this even possible? Polygons that cross the anti-meridian?
logger.log(Level.SEVERE, "Failed to simplify to convex hull: " + input.toText(), e2);
return input; // Garbage In, Garbage Out
}
}
}
// TODO avoid creating triangles on long straight edges
public static Polygon simplifyInternal(Polygon original, double areaThreshold, double lineThreshold) {
GeometryFactory gf = new GeometryFactory();
Geometry excesses, excess, keepTotal, keepA, keepB, chA, chB, keep = null, elim = null;
Polygon simplified = null, wrapper = (Polygon) original.convexHull();
try {
boolean done = false;
while (!done) {
done = true;
excesses = wrapper.difference(original);
for (int i = 0; i < excesses.getNumGeometries(); i++) {
excess = excesses.getGeometryN(i);
if (excess.getArea() / original.getArea() > areaThreshold) {
done = false; // excess too big - try to split then shrink
keepTotal = excess.intersection(original);
keepA = gf.createGeometryCollection(null);
keepB = gf.createGeometryCollection(null);
for (int j = 0; j < keepTotal.getNumGeometries(); j++) {
if (j < keepTotal.getNumGeometries() / 2) {
keepA = keepA.union(keepTotal.getGeometryN(j));
}
else {
keepB = keepB.union(keepTotal.getGeometryN(j));
}
}
chA = keepA.convexHull();
chB = keepB.convexHull();
keep = gf.createMultiPolygon(null);
if (chA instanceof Polygon) {
keep = keep.union(chA);
}
if (chB instanceof Polygon) {
keep = keep.union(chB);
}
elim = excess.difference(keep);
wrapper = (Polygon) wrapper.difference(elim);
}
}
}
new Assert(wrapper.getArea() >= original.getArea());
new Assert(wrapper.getArea() <= original.convexHull().getArea());
simplified = (Polygon) com.vividsolutions.jts.simplify.TopologyPreservingSimplifier.simplify(wrapper, lineThreshold);
new Assert(simplified.getNumPoints() <= original.getNumPoints());
new Assert(simplified.getNumInteriorRing() == 0);
new Assert(simplified.isSimple());
return simplified;
}
catch (Exception e) {
if (original.isSimple()) {
StringBuilder sb = new StringBuilder();
sb.append("Failed to simplify non-complex polygon!");
sb.append("\noriginal: " + original.toText());
sb.append("\nwrapper: " + (null == wrapper ? "" : wrapper.toText()));
sb.append("\nsimplified: " + (null == simplified ? "" : simplified.toText()));
sb.append("\nkeep: " + (null == keep ? "" : keep.toText()));
sb.append("\nelim: " + (null == elim ? "" : elim.toText()));
logger.log(Level.SEVERE, sb.toString());
}
throw e;
}
}
}