/*
* Copyright 2013 Hannes Janetzek
*
* This file is part of the OpenScienceMap project (http://www.opensciencemap.org).
*
* This program 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, either version 3 of the License, or (at your option) any later version.
*
* This program 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.
*
* You should have received a copy of the GNU Lesser General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.oscim.core;
/* TODO
* - check indexPos < Short.Max
* - should make internals private, maybe
*/
/**
* The GeometryBuffer class holds temporary geometry data for processing.
* Only One geometry type can be set at a time. Use 'clear()' to reset the
* internal state.
* <p>
* 'points[]' holds interleaved x,y coordinates
* <p>
* 'index[]' is used to store number of points within a geometry and encode
* multi-linestrings and (multi-)polygons.
*/
public class GeometryBuffer {
private final static int GROW_INDICES = 64;
private final static int GROW_POINTS = 512;
/**
* The Enum GeometryType.
*/
public enum GeometryType {
NONE(0),
POINT(1),
LINE(2),
POLY(3),
TRIS(4);
private GeometryType(int type) {
nativeInt = type;
}
public final int nativeInt;
}
/** The points. */
public float[] points;
/** The indexes. */
public int[] index;
/** The current index position. */
public int indexPos;
/** The current position in points array. */
public int pointPos;
/** The current geometry type. */
public GeometryType type;
private PointF mTmpPoint = new PointF();
private int pointLimit;
/**
* Instantiates a new geometry buffer.
*
* @param points the points
* @param index the index
*/
public GeometryBuffer(float[] points, int[] index) {
if (points == null)
points = new float[GROW_POINTS];
if (index == null)
index = new int[GROW_INDICES];
this.points = points;
this.index = index;
this.type = GeometryType.NONE;
this.indexPos = 0;
this.pointPos = 0;
this.pointLimit = points.length - 2;
}
/**
* @param out PointF to set coordinates to.
* @return when out is null a temporary PointF is
* returned which belongs to GeometryBuffer.
*/
public void getPoint(int i, PointF out) {
out.x = points[(i << 1)];
out.y = points[(i << 1) + 1];
}
public float getPointX(int i) {
return points[(i << 1)];
}
public float getPointY(int i) {
return points[(i << 1) + 1];
}
/**
* @return PointF belongs to GeometryBuffer.
*/
public PointF getPoint(int i) {
PointF out = mTmpPoint;
out.x = points[(i << 1)];
out.y = points[(i << 1) + 1];
return out;
}
public int getNumPoints() {
return pointPos >> 1;
}
/**
* Instantiates a new geometry buffer.
*
* @param numPoints the num points
* @param numIndices the num indices
*/
public GeometryBuffer(int numPoints, int numIndices) {
this(new float[numPoints * 2], new int[numIndices]);
}
/**
* Reset buffer.
*/
public void clear() {
index[0] = 0;
indexPos = 0;
pointPos = 0;
type = GeometryType.NONE;
}
/**
* Adds a point with the coordinate x, y.
*
* @param x the x ordinate
* @param y the y ordinate
*/
public GeometryBuffer addPoint(float x, float y) {
if (pointPos > pointLimit)
ensurePointSize((pointPos >> 1) + 1, true);
points[pointPos++] = x;
points[pointPos++] = y;
index[indexPos] += 2;
return this;
}
public boolean isPoly() {
return type == GeometryType.POLY;
}
public boolean isLine() {
return type == GeometryType.LINE;
}
public boolean isPoint() {
return type == GeometryType.POINT;
}
/**
* Sets the point x,y at position pos.
*
* @param pos the pos
* @param x the x ordinate
* @param y the y ordinate
*/
public void setPoint(int pos, float x, float y) {
points[(pos << 1) + 0] = x;
points[(pos << 1) + 1] = y;
}
/**
* Set geometry type for points.
*/
public void startPoints() {
setOrCheckMode(GeometryType.POINT);
}
/**
* Start a new line. Sets geometry type for lines.
*/
public GeometryBuffer startLine() {
setOrCheckMode(GeometryType.LINE);
/* ignore */
if (index[indexPos] > 0) {
/* start next */
if ((index[0] >= 0) && (++indexPos >= index.length))
ensureIndexSize(indexPos, true);
/* initialize with zero points */
index[indexPos] = 0;
}
/* set new end marker */
if (index.length > indexPos + 1)
index[indexPos + 1] = -1;
return this;
}
/**
* Start a new polygon. Sets geometry type for polygons.
*/
public GeometryBuffer startPolygon() {
boolean start = (type == GeometryType.NONE);
setOrCheckMode(GeometryType.POLY);
if ((indexPos + 3) > index.length)
ensureIndexSize(indexPos + 2, true);
if (!start && index[indexPos] != 0) {
/* end polygon */
index[++indexPos] = 0;
/* next polygon start */
indexPos++;
}
/* initialize with zero points */
index[indexPos] = 0;
/* set new end marker */
if (index.length > indexPos + 1)
index[indexPos + 1] = -1;
return this;
}
/**
* Starts a new polygon hole (inner ring).
*/
public void startHole() {
checkMode(GeometryType.POLY);
if ((indexPos + 2) > index.length)
ensureIndexSize(indexPos + 1, true);
/* initialize with zero points */
index[++indexPos] = 0;
/* set new end marker */
if (index.length > indexPos + 1)
index[indexPos + 1] = -1;
}
public GeometryBuffer translate(float dx, float dy) {
for (int i = 0; i < pointPos; i += 2) {
points[i] += dx;
points[i + 1] += dy;
}
return this;
}
public GeometryBuffer scale(float scaleX, float scaleY) {
for (int i = 0; i < pointPos; i += 2) {
points[i] *= scaleX;
points[i + 1] *= scaleY;
}
return this;
}
/**
* Ensure that 'points' array can hold the number of points.
*
* @param size the number of points to hold
* @param copy the the current data when array is reallocated
* @return the float[] array holding current coordinates
*/
public float[] ensurePointSize(int size, boolean copy) {
if (size * 2 < points.length)
return points;
size = size * 2 + GROW_POINTS;
float[] newPoints = new float[size];
if (copy)
System.arraycopy(points, 0, newPoints, 0, points.length);
points = newPoints;
pointLimit = size - 2;
return points;
}
/**
* Ensure index size.
*
* @param size the size
* @param copy the copy
* @return the short[] array holding current index
*/
public int[] ensureIndexSize(int size, boolean copy) {
if (size < index.length)
return index;
int[] newIndex = new int[size + GROW_INDICES];
if (copy)
System.arraycopy(index, 0, newIndex, 0, index.length);
index = newIndex;
return index;
}
private void setOrCheckMode(GeometryType m) {
if (type == m)
return;
if (type != GeometryType.NONE)
throw new IllegalArgumentException("not cleared " + m + "<>" + type);
type = m;
}
private void checkMode(GeometryType m) {
if (type != m)
throw new IllegalArgumentException("not cleared " + m + "<>" + type);
}
public void addPoint(Point p) {
addPoint((float) p.x, (float) p.y);
}
public void addPoint(PointF p) {
addPoint(p.x, p.y);
}
/**
* Remove points with distance less than minSqDist
*
* TODO could avoid superfluous copying
*
* @param minSqDist
* @param keepLines keep endpoint when line would
* otherwise collapse into a single point
*/
public void simplify(float minSqDist, boolean keepLines) {
int outPos = 0;
int inPos = 0;
for (int idx = 0; idx < index.length; idx++) {
if (index[idx] < 0)
break;
if (index[idx] == 0)
continue;
int first = inPos;
float px = points[inPos++];
float py = points[inPos++];
/* add first point */
points[outPos++] = px;
points[outPos++] = py;
int cnt = 2;
for (int pt = 2, end = index[idx]; pt < end; pt += 2) {
float cx = points[inPos++];
float cy = points[inPos++];
float dx = cx - px;
float dy = cy - py;
if ((dx * dx + dy * dy) < minSqDist) {
if (!keepLines || (pt < end - 2))
continue;
}
px = cx;
py = cy;
points[outPos++] = cx;
points[outPos++] = cy;
cnt += 2;
}
if ((type == GeometryType.POLY) &&
(points[first] == px) &&
(points[first + 1] == py)) {
/* remove identical start/end point */
cnt -= 2;
outPos -= 2;
}
index[idx] = cnt;
}
}
public String toString() {
StringBuffer sb = new StringBuffer();
int o = 0;
for (int i = 0; i < index.length; i++) {
if (index[i] < 0)
break;
if (index[i] == 0)
continue;
sb.append(":");
sb.append(index[i]);
sb.append('\n');
for (int j = 0; j < index[i]; j += 2) {
sb.append('[')
.append(points[o + j])
.append(',')
.append(points[o + j + 1])
.append(']');
if (j % 4 == 0)
sb.append('\n');
}
sb.append('\n');
o += index[i];
}
return sb.toString();
}
}