Есть ли способ преобразовать самопересекающийся многоугольник в мультиполигон в JTS?

Возьмем недопустимый многоугольник POLYGON((0 100, 100 100, 0 0, 100 0, 0 100)) — форму таймера яйца с необъявленной точкой пересечения.

Недопустимый многоугольник

Во многих инструкциях говорится, что JTS может создать действительную версию этого, используя метод buffer:

Geometry input = new WKTReader().read("POLYGON((0 100, 100 100, 0 0, 100 0, 0 100))");
Geometry output = geom.buffer(0);
return output;

Однако это приводит к выводу POLYGON ((0 100, 100 100, 50 50, 0 100)), где часть многоугольника потеряна:

Часть полигона потеряна

Есть ли способ заставить JTS проверять полигоны таким образом, чтобы он выдавал результат MULTIPOLYGON(((0 100, 100 100, 50 50, 0 100)), ((0 0, 100 0, 50 50, 0 0))) для заданного ввода?

Желаемый результат

Это похоже на то, что должно быть встроено в API (возможно, такое поведение является ошибкой) - я что-то пропустил?

Спасибо.


person tofarr    schedule 17.07.2015    source источник
comment
На самом деле это решение неверно. Какие части исходной самопересекающейся формы вам нужны? Код не может этого знать. Если вы примените это к ГИС, проблема станет еще больше. Самопересекающиеся полигоны запрещены правилами OGC по очень и очень веской причине.   -  person Corrections    schedule 07.03.2017


Ответы (1)


JTS, кажется, предлагает поведение, которое мне нужно, хотя мне пришлось немного поработать в своем собственном коде. Функция validate, которую я написал, разбивает полигон/мультиполигон на набор несамопересекающихся строк линий, а затем использует класс Polygonizer для построения полигонов из результата. Я протестировал его на следующем (ограниченном) наборе входных данных, и, похоже, он ведет себя так, как мне нужно:

    POLYGON((0 100, 100 100, 0 0, 100 0, 0 100))
    POLYGON((0 0, 0 100, 100 100, 100 0, 0 0))
    MULTIPOLYGON(((0 0, 0 100, 100 100, 100 0, 0 0)),((50 50, 50 150, 150 150, 150 50, 50 50)))
    POLYGON((0 0, 50 50, 100 0, 150 0, 200 50, 250 0, 0 0))

Код:

/**
 * Get / create a valid version of the geometry given. If the geometry is a polygon or multi polygon, self intersections /
 * inconsistencies are fixed. Otherwise the geometry is returned.
 * 
 * @param geom
 * @return a geometry 
 */
public static Geometry validate(Geometry geom){
    if(geom instanceof Polygon){
        if(geom.isValid()){
            geom.normalize(); // validate does not pick up rings in the wrong order - this will fix that
            return geom; // If the polygon is valid just return it
        }
        Polygonizer polygonizer = new Polygonizer();
        addPolygon((Polygon)geom, polygonizer);
        return toPolygonGeometry(polygonizer.getPolygons(), geom.getFactory());
    }else if(geom instanceof MultiPolygon){
        if(geom.isValid()){
            geom.normalize(); // validate does not pick up rings in the wrong order - this will fix that
            return geom; // If the multipolygon is valid just return it
        }
        Polygonizer polygonizer = new Polygonizer();
        for(int n = geom.getNumGeometries(); n-- > 0;){
            addPolygon((Polygon)geom.getGeometryN(n), polygonizer);
        }
        return toPolygonGeometry(polygonizer.getPolygons(), geom.getFactory());
    }else{
        return geom; // In my case, I only care about polygon / multipolygon geometries
    }
}

/**
 * Add all line strings from the polygon given to the polygonizer given
 * 
 * @param polygon polygon from which to extract line strings
 * @param polygonizer polygonizer
 */
static void addPolygon(Polygon polygon, Polygonizer polygonizer){
    addLineString(polygon.getExteriorRing(), polygonizer);
    for(int n = polygon.getNumInteriorRing(); n-- > 0;){
        addLineString(polygon.getInteriorRingN(n), polygonizer);
    }
}

/**
 * Add the linestring given to the polygonizer
 * 
 * @param linestring line string
 * @param polygonizer polygonizer
 */
static void addLineString(LineString lineString, Polygonizer polygonizer){

    if(lineString instanceof LinearRing){ // LinearRings are treated differently to line strings : we need a LineString NOT a LinearRing
        lineString = lineString.getFactory().createLineString(lineString.getCoordinateSequence());
    }

    // unioning the linestring with the point makes any self intersections explicit.
    Point point = lineString.getFactory().createPoint(lineString.getCoordinateN(0));
    Geometry toAdd = lineString.union(point); 

    //Add result to polygonizer
    polygonizer.add(toAdd);
}

/**
 * Get a geometry from a collection of polygons.
 * 
 * @param polygons collection
 * @param factory factory to generate MultiPolygon if required
 * @return null if there were no polygons, the polygon if there was only one, or a MultiPolygon containing all polygons otherwise
 */
static Geometry toPolygonGeometry(Collection<Polygon> polygons, GeometryFactory factory){
    switch(polygons.size()){
        case 0:
            return null; // No valid polygons!
        case 1:
            return polygons.iterator().next(); // single polygon - no need to wrap
        default:
            //polygons may still overlap! Need to sym difference them
            Iterator<Polygon> iter = polygons.iterator();
            Geometry ret = iter.next();
            while(iter.hasNext()){
                ret = ret.symDifference(iter.next());
            }
            return ret;
    }
}
person tofarr    schedule 17.07.2015
comment
Хорошо, я знаю, что это не должно быть в комментарии, но... Как мне купить тебе пива? (я на 100% серьезно) - person Sergio; 08.02.2016
comment
Как я могу заставить его работать для чего-то вроде: POLYGON((0 100, 100 100, 0 0, 100 0, 0 100), (55 20, 45 10, 55 10, 45 20, 55 20)) - person dmitryzv; 18.01.2018