This is an automated email from the ASF dual-hosted git repository.

jsorel pushed a commit to branch feat/image2polygon
in repository https://gitbox.apache.org/repos/asf/sis.git

commit fe6bca1e57746dbee06ae3501c3781309409ac48
Author: jsorel <johann.so...@geomatys.com>
AuthorDate: Tue Mar 25 11:50:05 2025 +0100

    Add image to polygon area algorithm
---
 .../main/org/apache/sis/image/ImageProcessor.java  |  39 +++
 .../apache/sis/image/processing/polygon/Block.java |  42 +++
 .../sis/image/processing/polygon/Boundary.java     | 349 +++++++++++++++++++
 .../sis/image/processing/polygon/Polygonize.java   | 384 +++++++++++++++++++++
 4 files changed, 814 insertions(+)

diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
index 9cdae190a2..1345b4075d 100644
--- 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/ImageProcessor.java
@@ -56,8 +56,10 @@ import org.apache.sis.util.collection.WeakHashSet;
 import org.apache.sis.system.Modules;
 import org.apache.sis.image.processing.isoline.Isolines;
 import org.apache.sis.feature.internal.Resources;
+import org.apache.sis.image.processing.polygon.Polygonize;
 import org.apache.sis.measure.NumberRange;
 import org.apache.sis.measure.Units;
+import org.opengis.referencing.operation.MathTransform2D;
 
 
 /**
@@ -1540,6 +1542,43 @@ public class ImageProcessor implements Cloneable {
         }
     }
 
+    /**
+     * Generates area polygons at the specified ranges computed from data 
provided by the given image.
+     * Polygons will be computed for every bands in the given image.
+     * For each band, the result is given as a {@code Map} where keys are the 
specified {@code ranges}
+     * and values are the polygons at the associated range.
+     * If there are no polygons for a given level, there will be no 
corresponding entry in the map.
+     * The provided {@code ranges} must not overlap each other.
+     *
+     * @param  data       image providing source values.
+     * @param  ranges    value ranges for which to compute polygones. An array 
should be provided for each band.
+     *                    If there is more bands than {@code ranges.length}, 
the last array is reused for
+     *                    all remaining bands.
+     * @param  gridToCRS  transform from pixel coordinates to geometry 
coordinates, or {@code null} if none.
+     *                    Integer source coordinates are located at pixel 
centers.
+     * @return the polygons for specified ranges in each band. The {@code 
List} size is the number of bands.
+     *         For each band, the {@code Map} size is equal or less than 
{@code ranges[band].length}.
+     *         Map keys are the specified ranges, excluding those for which 
there are no polygons.
+     *         Map values are the polygons as a Java2D {@link Shape}.
+     * @throws ImagingOpException if an error occurred during calculation.
+     */
+    public List<Map<NumberRange,List<Shape>>> areas(final RenderedImage data, 
NumberRange[][] ranges, final MathTransform gridToCRS) throws 
TransformException {
+        final Polygonize polygonizer = new Polygonize(data, ranges);
+        final List<Map<NumberRange, List<Shape>>> result = 
polygonizer.polygones();
+        
+        if (gridToCRS != null && !gridToCRS.isIdentity()) {
+            final MathTransform2D trs2d = 
MathTransforms.bidimensional(gridToCRS);
+            for (Map<NumberRange, List<Shape>> m : result) {
+                for (List<Shape> lst : m.values()) {
+                    for (int i = 0, n = lst.size(); i < n; i++) {
+                        lst.set(i, trs2d.createTransformedShape(lst.get(i)));
+                    }
+                }
+            }
+        }
+        return result;
+    }
+    
     /**
      * Returns {@code true} if the given object is an image processor
      * of the same class with the same configuration.
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
new file mode 100644
index 0000000000..45cbea909d
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Block.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image.processing.polygon;
+
+import org.apache.sis.measure.NumberRange;
+
+/**
+ * Define a group of pixels with the same range.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class Block {
+
+    public NumberRange range;
+    public int startX;
+    public int endX;
+    public int y;
+    public Boundary boundary;
+
+    public void reset(){
+        range = null;
+        startX = -1;
+        endX = -1;
+        y = -1;
+        boundary = null;
+    }
+
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
new file mode 100644
index 0000000000..5a4ce953e9
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Boundary.java
@@ -0,0 +1,349 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image.processing.polygon;
+
+import java.awt.Shape;
+import java.awt.geom.Area;
+import java.awt.geom.GeneralPath;
+import java.awt.geom.Point2D;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
+import java.util.List;
+import org.apache.sis.measure.NumberRange;
+
+/**
+ * Define an "in construction" geometries.
+ * This type of objects contain a list of floeatings coordinates sequences
+ * that are progressivly merged together to obtain a polygon which might 
contain holes.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+final class Boundary {
+
+    //finished geometries
+    private final List<GeneralPath> holes = new ArrayList<>();
+
+    //in construction geometries
+    private final LinkedList<LinkedList<Point2D.Double>> floatings = new 
LinkedList<LinkedList<Point2D.Double>>();
+    final NumberRange range;
+
+    public Boundary(final NumberRange range){
+        this.range = range;
+    }
+
+    public void start(final int firstX, final int secondX, final int y){
+        if(firstX == secondX) throw new IllegalArgumentException("bugging 
algorithm");
+        final LinkedList<Point2D.Double> exterior = new 
LinkedList<Point2D.Double>();
+        exterior.addFirst(new Point2D.Double(firstX, y));
+        exterior.addFirst(new Point2D.Double(firstX, y+1));
+        exterior.addLast(new Point2D.Double(secondX, y));
+        exterior.addLast(new Point2D.Double(secondX, y+1));
+        floatings.add(exterior);
+
+        //checkValidity();
+    }
+
+    public void addFloating(final Point2D.Double from, final Point2D.Double 
to){
+        if (from.equals(to)) {
+            throw new IllegalArgumentException("bugging algorithm");
+        }
+        //System.err.println("Add Floating From: " + from + " New : " + to );
+
+        final LinkedList<Point2D.Double> ll = new LinkedList<Point2D.Double>();
+        ll.addFirst(to);
+        ll.addLast(from);
+        floatings.add(ll);
+
+        //checkValidity();
+    }
+
+    public void add(final Point2D.Double from, final Point2D.Double to){
+        if(from.equals(to)){
+            throw new IllegalArgumentException("bugging algorithm");
+        }
+
+        //System.err.println("Add From: " + from + " New : " + to );
+
+
+        for(final LinkedList<Point2D.Double> ll : floatings){
+            final Point2D.Double first = ll.getFirst();
+            if(first.equals(from)){
+
+                if(from.x == to.x && ll.size() > 1){
+                    final Point2D.Double second = ll.get(1);
+                    if(second.x == from.x){
+                        //points are aligned, just move the first point
+                        first.y = to.y;
+                    }else{
+                        //points are not aligned, must create a new point
+                        ll.addFirst(to);
+                    }
+                }else if(from.y == to.y && ll.size() > 1){
+                    final Point2D.Double second = ll.get(1);
+                    if(second.y == from.y){
+                        //points are aligned, just move the first point
+                        first.x = to.x;
+                    }else{
+                        //points are not aligned, must create a new point
+                        ll.addFirst(to);
+                    }
+                }else{
+                    ll.addFirst(to);
+                }
+
+                //checkValidity();
+                return;
+            }
+
+            final Point2D.Double last = ll.getLast();
+            if(last.equals(from)){
+                if(from.x == to.x && ll.size() > 1){
+                    final Point2D.Double second = ll.get(ll.size()-2);
+                    if(second.x == from.x){
+                        //points are aligned, just move the first point
+                        last.y = to.y;
+                    }else{
+                        //points are not aligned, must create a new point
+                        ll.addLast(to);
+                    }
+                }else if(from.y == to.y && ll.size() > 1){
+                    final Point2D.Double second = ll.get(ll.size()-2);
+                    if(second.y == from.y){
+                        //points are aligned, just move the first point
+                        last.x = to.x;
+                    }else{
+                        //points are not aligned, must create a new point
+                        ll.addLast(to);
+                    }
+                }else{
+                    ll.addLast(to);
+                }
+
+                //checkValidity();
+                return;
+            }
+        }
+
+        //checkValidity();
+        throw new IllegalArgumentException("bugging algorithm");
+    }
+
+    public Shape link(final Point2D.Double from, final Point2D.Double to){
+        if(from.equals(to)){
+            throw new IllegalArgumentException("bugging algorithm : " + to);
+        }
+
+        //System.err.println("Link From: " + from + " to : " + to );
+
+
+        LinkedList<Point2D.Double> fromList = null;
+        boolean fromStart = false;
+        LinkedList<Point2D.Double> toList = null;
+        boolean toStart = false;
+
+        for(final LinkedList<Point2D.Double> ll : floatings){
+
+            if(fromList == null){
+                final Point2D.Double first = ll.getFirst();
+                final Point2D.Double last = ll.getLast();
+                if(first.equals(from)){
+                    fromStart = true;
+                    fromList = ll;
+                }else if(last.equals(from)){
+                    fromStart = false;
+                    fromList = ll;
+                }
+            }
+
+            if(toList == null){
+                final Point2D.Double first = ll.getFirst();
+                final Point2D.Double last = ll.getLast();
+                if(first.equals(to)){
+                    toStart = true;
+                    toList = ll;
+                }else if(last.equals(to)){
+                    toStart = false;
+                    toList = ll;
+                }
+            }
+
+            if(fromList != null && toList != null) break;
+        }
+
+
+        if(fromList != null && toList != null){
+            if(fromList == toList){
+                //same list finish it
+                //checkValidity();
+                return finish(fromList);
+            }else{
+                combine(fromList, fromStart, toList, toStart);
+                //checkValidity();
+                return null;
+            }
+
+        }else if(fromList != null ){
+            add(from, to);
+            //checkValidity();
+            return null;
+        }else if(toList != null){
+            add(to, from);
+            //checkValidity();
+            return null;
+        }
+
+        //checkValidity();
+        throw new IllegalArgumentException("bugging algorithm");
+    }
+
+    private void combine(final LinkedList<Point2D.Double> fromList, final 
boolean fromStart,
+                         final LinkedList<Point2D.Double> toList, final 
boolean toStart){
+
+        if(fromStart){
+            if(toStart){
+                while(!toList.isEmpty()){
+                    fromList.addFirst(toList.pollFirst());
+                }
+            }else{
+                while(!toList.isEmpty()){
+                    fromList.addFirst(toList.pollLast());
+                }
+            }
+
+        }else{
+            if(toStart){
+                while(!toList.isEmpty()){
+                    fromList.addLast(toList.pollFirst());
+                }
+            }else{
+                while(!toList.isEmpty()){
+                    fromList.addLast(toList.pollLast());
+                }
+            }
+        }
+
+        floatings.remove(toList);
+        //checkValidity();
+    }
+
+
+    public void checkValidity(){
+
+        //check for list with less than 2 elements
+        for(LinkedList<Point2D.Double> ll : floatings){
+            if(ll.size() < 2){
+                //System.err.println(">>>> ERROR : " + this.toString());
+                throw new IllegalArgumentException("What ? a list with less 
than 2 elements, not valid !");
+            }
+        }
+
+        //check for diagonal cases
+        for(LinkedList<Point2D.Double> ll : floatings){
+            Point2D.Double last = ll.get(0);
+            for(int i=1;i<ll.size();i++){
+                Point2D.Double current = ll.get(i);
+                if(last.x != current.x && last.y != current.y){
+                    //System.err.println(">>>> ERROR : " + this.toString());
+                    throw new IllegalArgumentException("What ? a diagonal, not 
valid !");
+                }
+                last = current;
+            }
+        }
+
+    }
+
+
+
+    private Shape finish(final LinkedList<Point2D.Double> coords){
+
+        if(floatings.size() == 1){
+            //closing the polygon enveloppe
+            Shape geom = toClosedShape(coords);
+            //ring.setUserData(range);
+            floatings.remove(coords);
+            
+            if (!holes.isEmpty()) {
+                Area area = new Area(geom);
+                for (Shape hole : holes) {
+                    area.subtract(new Area(hole));
+                }
+                geom = area;
+            }
+            
+            return geom;
+        }else{
+            //closing a hole in the geometry
+            GeneralPath ring = toClosedShape(coords);
+            holes.add(ring);
+            floatings.remove(coords);
+            return null;
+        }
+
+    }
+
+    private static void reverse(final Point2D.Double[] array){
+        for(int l=0, r=array.length-1; l<r; l++, r--) {
+            Point2D.Double temp = array[l];
+            array[l] = array[r];
+            array[r] = temp;
+        }
+    }
+    
+    private GeneralPath toClosedShape(List<Point2D.Double> points) {
+        final GeneralPath path = new GeneralPath();
+        Point2D.Double start = points.get(0);
+        path.moveTo(start.x, start.y);
+        for (int i = 1, n = points.size(); i < n; i++) {
+            Point2D.Double pt = points.get(i);
+            path.lineTo(pt.x, pt.y);
+        }
+        path.closePath();
+        
+        return path;
+    }
+
+    public void merge(final Boundary candidate){
+        //merge the floating sequences
+        this.floatings.addAll(candidate.floatings);
+        //merge the holes
+        this.holes.addAll(candidate.holes);
+        candidate.floatings.clear();
+        candidate.holes.clear();
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder sb = new StringBuilder("Boundary : ");
+        sb.append(range.toString());
+
+        for(LinkedList<Point2D.Double> coords : floatings){
+            sb.append("  \t{");
+            for(Point2D.Double c : coords){
+                
sb.append('[').append((int)c.x).append(';').append((int)c.y).append(']');
+            }
+            sb.append('}');
+        }
+
+        return sb.toString();
+    }
+
+    public boolean isEmpty(){
+        return floatings.isEmpty();
+    }
+
+}
diff --git 
a/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
new file mode 100644
index 0000000000..1782f30357
--- /dev/null
+++ 
b/endorsed/src/org.apache.sis.feature/main/org/apache/sis/image/processing/polygon/Polygonize.java
@@ -0,0 +1,384 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.sis.image.processing.polygon;
+
+import java.awt.Point;
+import java.awt.Shape;
+import java.awt.geom.Point2D;
+import java.awt.image.RenderedImage;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.apache.sis.measure.NumberRange;
+import org.apache.sis.image.PixelIterator;
+import org.opengis.coverage.grid.SequenceType;
+
+
+/**
+ * Process to extract Polygon from an image.
+ *
+ * @author Johann Sorel (Geomatys)
+ */
+public final class Polygonize {
+
+    private static final int LAST_LINE = 0;
+    private static final int CURRENT_LINE = 1;
+
+    //last line cache boundary
+    private final List<Map<NumberRange, List<Shape>>> polygons = new 
ArrayList<>();
+
+    //buffer[band][LAST_LINE] holds last line buffer
+    //buffer[band][CURRENT_LINE] holds current line buffer
+    private Boundary[][][] buffers;
+
+    //current pixel block
+    private Block[] blocks;
+
+    private final RenderedImage image;
+    private final NumberRange[][] ranges;
+    
+    /**
+     *
+     * @param coverage coverage to process
+     * @param ranges data value ranges
+     * @param band coverage band to process
+     */
+    public Polygonize(RenderedImage image, NumberRange[][] ranges){
+        this.image = image;
+        this.ranges = ranges;
+    }
+    
+    public List<Map<NumberRange, List<Shape>>> polygones() {
+
+        final PixelIterator iter = new 
PixelIterator.Builder().setIteratorOrder(SequenceType.LINEAR).create(image);
+        final int nbBand = iter.getNumBands();
+        blocks = new Block[nbBand];
+        
+        final NumberRange NaNRange = new NaNRange();
+        for (int band = 0; band < nbBand; band++) {            
+            final Map<NumberRange, List<Shape>> bandState = new HashMap<>();   
         
+            //add a range for Nan values.            
+            bandState.put(NaNRange, new ArrayList<>());
+            polygons.add(bandState);
+
+            for (final NumberRange range : ranges[Math.min(band, 
ranges.length-1)]) {
+                bandState.put(range, new ArrayList<>());
+            }
+            
+            blocks[band] = new Block();
+        }
+        
+        
+        /*
+         This algorithm create polygons which follow the contour of each pixel.
+         The 0,0 coordinate will match the pixel corner.
+        */
+        final Point gridPosition = new Point(0, 0);
+        double[] pixel = null;
+
+        final int width = image.getWidth();
+        final int height = image.getHeight();
+        buffers = new Boundary[nbBand][2][width];
+        
+        for (int y = 0; y < height; y++) {
+            iter.moveTo(0, y);
+            gridPosition.y = y;
+            for (int x = 0; x < width; x++) {
+                gridPosition.x = x;
+                pixel = iter.getPixel(pixel);
+                for (int band = 0; band < nbBand; band++) {
+                    append(polygons.get(band), buffers[band], blocks[band], 
gridPosition, pixel[band]);
+                }
+                iter.next();
+            }
+                        
+            //insert last geometry
+            for (int band = 0; band < nbBand; band++) {
+                constructBlock(polygons.get(band), blocks[band], 
buffers[band]);
+                
+                //flip buffers, reuse old buffer line.
+                Boundary[] oldLine = buffers[band][LAST_LINE];
+                buffers[band][LAST_LINE] = buffers[band][CURRENT_LINE];
+                buffers[band][CURRENT_LINE] = oldLine;
+
+                blocks[band].reset();    
+            }
+        
+        }
+        
+        //we have finish, close all geometries
+        gridPosition.y += 1;
+        for (int band = 0; band < nbBand; band++) {
+            for(int i = 0; i < buffers[band][LAST_LINE].length;i++) {
+                Shape poly = buffers[band][LAST_LINE][i].link(
+                        new Point2D.Double(i, gridPosition.y),
+                        new Point2D.Double(i+1, gridPosition.y)
+                        );
+                if (poly != null) {
+                    
polygons.get(band).get(buffers[band][LAST_LINE][i].range).add(poly);
+                }
+            }
+        }
+        
+        //avoid memory use
+        buffers = null;
+        final List<Map<NumberRange, List<Shape>>> copy = new 
ArrayList<>(polygons);
+        //remove the NaNRange
+        for (Map m : copy) {
+            m.remove(NaNRange);
+        }
+        polygons.clear();        
+        return copy;
+    }
+
+    private static void append(Map<NumberRange, List<Shape>> results, final 
Boundary[][] buffers, final Block block, final Point point, Number value) {
+
+        //special case for NaN or null
+        final NumberRange valueRange;
+        if (value == null || Double.isNaN(value.doubleValue())) {
+            valueRange = new NaNRange();
+        } else {
+            valueRange = results.keySet().stream()
+                    .filter(range -> range.containsAny(value))
+                    .findAny()
+                    .orElseThrow(() -> new IllegalArgumentException("Value not 
in any range :" + value));
+        }
+
+        if (valueRange.equals(block.range)) {
+            //last pixel was in the same range
+            block.endX = point.x;
+            return;
+        } else if (block.range != null) {
+            //last pixel was in a different range, save it's geometry
+            constructBlock(results, block, buffers);
+        }
+
+        //start a pixel serie
+        block.range = valueRange;
+        block.startX = point.x;
+        block.endX = point.x;
+        block.y = point.y;
+    }
+
+    private static void constructBlock(Map<NumberRange, List<Shape>> results, 
final Block block, final Boundary[][] buffers) {
+
+        //System.err.println("BLOCK ["+block.startX+","+block.endX+"]");
+
+        if(block.y == 0) {
+            //first line, the buffer is empty, must fill it
+            final Boundary boundary = new Boundary(block.range);
+            boundary.start(block.startX, block.endX+1, block.y);
+
+            for(int i=block.startX; i<=block.endX; i++) {
+                buffers[CURRENT_LINE][i] = boundary;
+            }
+        }else{
+            Boundary currentBoundary = null;
+
+            //first pass to close unfriendly blocks 
----------------------------
+            for (int i=block.startX; i<=block.endX;) {
+                final Boundary candidate = buffers[LAST_LINE][i];
+                final int[] candidateExtent = findExtent(buffers, i);
+
+                //do not treat same blockes here
+                if (candidate.range != block.range) {
+                    //System.err.println("A different block extent : "+ 
candidateExtent[0] + " " + candidateExtent[1]);
+                    //System.err.println("before :" + candidate.toString());
+
+                    if (candidateExtent[0] >= block.startX && 
candidateExtent[1] <= block.endX) {
+                        //block overlaps completly candidate
+                        final Shape poly = candidate.link(
+                                new Point2D.Double(candidateExtent[0], 
block.y),
+                                new Point2D.Double(candidateExtent[1]+1, 
block.y)
+                                );
+                        if(poly != null) 
results.get(candidate.range).add(poly);
+                    } else {
+                        final Shape poly = candidate.link(
+                                new Point2D.Double( 
(block.startX<candidateExtent[0]) ? candidateExtent[0]: block.startX, block.y),
+                                new Point2D.Double( 
(block.endX>candidateExtent[1]) ? candidateExtent[1]+1: block.endX+1, block.y)
+                                );
+                        if (poly != null) 
results.get(candidate.range).add(poly);
+                    }
+
+                    //System.err.println("after :" + candidate.toString());
+                }
+
+                i = candidateExtent[1]+1;
+            }
+
+            //second pass to fuse with friendly blocks 
-------------------------
+
+            //we first merge the last line boundary if needed
+            int firstAnchor = Integer.MAX_VALUE;
+            int lastAnchor = Integer.MIN_VALUE;
+
+            for (int i = block.startX; i <= block.endX; ) {
+                final Boundary candidate = buffers[LAST_LINE][i];
+                final int[] candidateExtent = findExtent(buffers, i);
+
+                //do not treat different blocks here
+                if (candidate.range == block.range) {
+                    //System.err.println("A firnet block extent : "+ 
candidateExtent[0] + " " + candidateExtent[1]);
+//                    //System.err.println("before :" + candidate.toString());
+
+                    if (currentBoundary == null) {
+                        //set the current boundary, will expend this one
+                        currentBoundary = candidate;
+                    } else if(currentBoundary != null) {
+                        if(currentBoundary != candidate) {
+                            //those two blocks doesnt belong to the same 
boundaries, we must merge them
+                            currentBoundary.merge(candidate);
+                        }
+                        currentBoundary.link(
+                            new Point2D.Double(lastAnchor, block.y),
+                            new Point2D.Double(candidateExtent[0], block.y)
+                            );
+
+                        replaceInLastLigne(buffers, candidate, 
currentBoundary);
+                        //System.out.println("Merging : " + 
currentBoundary.toString());
+                    }
+
+                    if (candidateExtent[0] < firstAnchor) {
+                        firstAnchor = candidateExtent[0];
+                    }
+                    lastAnchor = candidateExtent[1]+1;
+                }
+
+                i = candidateExtent[1]+1;
+            }
+
+            if (currentBoundary != null) {
+                //System.err.println("before :" + currentBoundary.toString());
+            }
+
+            if (currentBoundary == null) {
+                //no previous friendly boundary to link with
+                //make a new one
+                currentBoundary = new Boundary(block.range);
+                currentBoundary.start(block.startX, block.endX+1, block.y);
+            } else {
+                if (firstAnchor < block.startX) {
+                    //the previous block has created a floating sequence to 
this end
+                    firstAnchor = block.startX;
+                }
+
+                //add the coordinates
+                //System.err.println("> first anchor : " +firstAnchor + " 
lastAnchor : " +lastAnchor);
+                if (firstAnchor == block.startX) {
+                    currentBoundary.add(
+                        new Point2D.Double(firstAnchor, block.y),
+                        new Point2D.Double(block.startX, block.y+1)
+                        );
+                } else {
+                    currentBoundary.add(
+                        new Point2D.Double(firstAnchor, block.y),
+                        new Point2D.Double(block.startX, block.y)
+                        );
+                    currentBoundary.add(
+                        new Point2D.Double(block.startX, block.y),
+                        new Point2D.Double(block.startX, block.y+1)
+                        );
+                }
+
+                if (block.endX+1 >= lastAnchor) {
+                    if (lastAnchor == block.endX+1) {
+                        currentBoundary.add(
+                            new Point2D.Double(lastAnchor, block.y),
+                            new Point2D.Double(block.endX+1, block.y+1)
+                            );
+                    } else {
+                        //System.err.println("0 add :" + 
currentBoundary.toString());
+                        currentBoundary.add(
+                            new Point2D.Double(lastAnchor, block.y),
+                            new Point2D.Double(block.endX+1, block.y)
+                            );
+                        //System.err.println("1 add:" + 
currentBoundary.toString());
+                        currentBoundary.add(
+                            new Point2D.Double(block.endX+1, block.y),
+                            new Point2D.Double(block.endX+1, block.y+1)
+                            );
+                        //System.err.println("after add:" + 
currentBoundary.toString());
+                    }
+                } else {
+                    currentBoundary.addFloating(
+                            new Point2D.Double(block.endX+1, block.y),
+                            new Point2D.Double(block.endX+1, block.y+1)
+                            );
+                }
+
+                //System.err.println(currentBoundary.toString());
+
+            }
+
+            //System.err.println("after :" + currentBoundary.toString());
+
+            //fill in the current line 
-----------------------------------------
+
+            for (int i = block.startX; i <= block.endX; i++) {
+                if (currentBoundary.isEmpty()) {
+                    throw new IllegalArgumentException("An empty boundary 
inserted ? not possible.");
+                }
+
+                buffers[CURRENT_LINE][i] = currentBoundary;
+            }
+
+        }
+
+    }
+
+    private static void replaceInLastLigne(final Boundary[][] buffers, final 
Boundary old, final Boundary newone) {
+        for (int i = 0, n = buffers[LAST_LINE].length; i < n; i++) {
+            if (buffers[LAST_LINE][i] == old) {
+                buffers[LAST_LINE][i] = newone;
+            }
+
+            if (buffers[CURRENT_LINE][i] == old) {
+                buffers[CURRENT_LINE][i] = newone;
+            }
+        }
+    }
+
+
+    private static int[] findExtent(final Boundary[][] buffers, final int 
index) {
+        final int[] extent = new int[]{index,index};
+        final Boundary bnd = buffers[LAST_LINE][index];
+
+        while (extent[0] > 0 && buffers[LAST_LINE][ extent[0]-1 ] == bnd) {
+            extent[0]--;
+        }
+
+        while (extent[1] < buffers[LAST_LINE].length-1 && buffers[LAST_LINE][ 
extent[1]+1 ] == bnd) {
+            extent[1]++;
+        }
+
+        return extent;
+    }
+
+    private static class NaNRange extends NumberRange{
+
+        public NaNRange() {
+            super(Double.class, 0d, true, 0d, true);
+        }
+
+        @Override
+        public boolean contains(final Comparable number) throws 
IllegalArgumentException {
+            return Double.isNaN(((Number) number).doubleValue());
+        }
+    }
+
+}

Reply via email to