/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2011, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.grid.ortholine;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.geotools.data.simple.SimpleFeatureCollection;
import org.geotools.feature.simple.SimpleFeatureBuilder;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.grid.GridFeatureBuilder;
/**
* A builder to generate a grid of horizontal and/or vertical ortho-lines.
*
* @author mbedward
* @since 8.0
*
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/grid/src/main/java/org/geotools/grid/ortholine/OrthoLineBuilder.java $
* @version $Id: Grids.java 37149 2011-05-10 11:47:02Z mbedward $
*/
public class OrthoLineBuilder {
private static final double TOL = 1.0e-8;
private final ReferencedEnvelope gridBounds;
private boolean hasVerticals;
private boolean hasHorizontals;
private boolean densify;
private SimpleFeatureBuilder featureBuilder;
/**
* Creates a new builder for the specified envelope.
*
* @param gridBounds bounds of the area for which lines will be generated
*/
public OrthoLineBuilder(ReferencedEnvelope gridBounds) {
this.gridBounds = gridBounds;
}
/**
* Creates line features according to the provided {@code OrthoLineDef} objects and
* places them into the feature collection.
* Densified lines (lines strings with additional vertices along their length) can be
* created by setting the value of {@code vertexSpacing} greater than zero; if so, any
* lines more than twice as long as this value will be densified.
*
* @param lineDefs line definitions specifying the orientation, spacing and level of lines
* @param lineFeatureBuilder the feature build to create {@code SimpleFeatures} from
* line elements
* @param vertexSpacing maximum distance between adjacent vertices along a line
* @param fc the feature collection into which generated line features are placed
*/
public void buildGrid(Collection<OrthoLineDef> lineDefs,
GridFeatureBuilder lineFeatureBuilder, double vertexSpacing, SimpleFeatureCollection fc) {
init(lineDefs, lineFeatureBuilder, vertexSpacing);
List<OrthoLineDef> horizontal = new ArrayList<OrthoLineDef>();
List<OrthoLineDef> vertical = new ArrayList<OrthoLineDef>();
for (OrthoLineDef lineDef : lineDefs) {
switch (lineDef.getOrientation()) {
case HORIZONTAL:
horizontal.add(lineDef);
break;
case VERTICAL:
vertical.add(lineDef);
break;
}
}
doBuildLineFeatures(horizontal, LineOrientation.HORIZONTAL,
lineFeatureBuilder, densify, vertexSpacing, fc);
doBuildLineFeatures(vertical, LineOrientation.VERTICAL,
lineFeatureBuilder, densify, vertexSpacing, fc);
}
private void doBuildLineFeatures(List<OrthoLineDef> lineDefs,
LineOrientation orientation,
GridFeatureBuilder lineFeatureBuilder,
boolean densify,
double vertexSpacing,
SimpleFeatureCollection fc) {
final int NDEFS = lineDefs.size();
if (NDEFS > 0) {
double minOrdinate, maxOrdinate;
if (orientation == LineOrientation.HORIZONTAL) {
minOrdinate = gridBounds.getMinY();
maxOrdinate = gridBounds.getMaxY();
} else {
minOrdinate = gridBounds.getMinX();
maxOrdinate = gridBounds.getMaxX();
}
double[] pos = new double[NDEFS];
boolean[] active = new boolean[NDEFS];
boolean[] atCurPos = new boolean[NDEFS];
boolean[] generate = new boolean[NDEFS];
Map<String, Object> attributes = new HashMap<String, Object>();
String geomPropName = lineFeatureBuilder.getType().getGeometryDescriptor().getLocalName();
for (int i = 0; i < NDEFS; i++) {
pos[i] = minOrdinate;
active[i] = true;
}
int numActive = NDEFS;
while (numActive > 0) {
/*
* Update scan position (curPos)
*/
double curPos = maxOrdinate;
for (int i = 0; i < NDEFS; i++) {
if (active[i] && pos[i] < curPos - TOL) {
curPos = pos[i];
}
}
/*
* Check which line elements are at the current scan position
*/
for (int i = 0; i < NDEFS; i++) {
atCurPos[i] = active[i] && Math.abs(pos[i] - curPos) < TOL;
}
/*
* Get line with highest precedence for the current position
*/
System.arraycopy(atCurPos, 0, generate, 0, NDEFS);
for (int i = 0; i < NDEFS - 1; i++) {
if (generate[i] && atCurPos[i]) {
for (int j = i + 1; j < NDEFS; j++) {
if (generate[j] && atCurPos[j]) {
if (lineDefs.get(i).getLevel() >= lineDefs.get(j).getLevel()) {
generate[j] = false;
} else {
generate[i] = false;
break;
}
}
}
} else {
generate[i] = false;
}
}
/*
* Create the line feature with highest precedence
*/
for (int i = 0; i < NDEFS; i++) {
if (generate[i]) {
OrthoLine element = new OrthoLine(gridBounds, orientation,
pos[i], lineDefs.get(i).getLevel());
if (lineFeatureBuilder.getCreateFeature(element)) {
lineFeatureBuilder.setAttributes(element, attributes);
if (densify) {
featureBuilder.set(geomPropName, element.toDenseGeometry(vertexSpacing));
} else {
featureBuilder.set(geomPropName, element.toGeometry());
}
for (String propName : attributes.keySet()) {
featureBuilder.set(propName, attributes.get(propName));
}
fc.add(featureBuilder.buildFeature(lineFeatureBuilder.getFeatureID(element)));
}
}
}
/*
* Update line element positions
*/
for (int i = 0; i < NDEFS; i++) {
if (atCurPos[i]) {
pos[i] += lineDefs.get(i).getSpacing();
if (pos[i] > maxOrdinate + TOL) {
active[i] = false;
numActive-- ;
}
}
}
}
}
}
private boolean isValidDenseVertexSpacing(double v) {
double minDim;
if (hasVerticals) {
if (hasHorizontals) {
minDim = Math.min(gridBounds.getWidth(), gridBounds.getHeight());
} else {
minDim = gridBounds.getHeight();
}
} else {
minDim = gridBounds.getWidth();
}
return v > 0 && v < minDim / 2;
}
private void init(Collection<OrthoLineDef> controls,
GridFeatureBuilder lineFeatureBuilder,
double vertexSpacing) {
if (gridBounds == null || gridBounds.isEmpty()) {
throw new IllegalArgumentException("gridBounds must not be null or empty");
}
if (controls == null || controls.isEmpty()) {
throw new IllegalArgumentException("required one or more line parameters");
}
for (OrthoLineDef param : controls) {
if (param.getOrientation() == LineOrientation.HORIZONTAL) {
hasHorizontals = true;
} else if (param.getOrientation() == LineOrientation.VERTICAL) {
hasVerticals = true;
} else {
throw new IllegalArgumentException(
"Only horizontal and vertical lines are supported");
}
}
densify = isValidDenseVertexSpacing(vertexSpacing);
featureBuilder = new SimpleFeatureBuilder(lineFeatureBuilder.getType());
}
}