/*******************************************************************************
* Copyright (c) 2015 MITRE
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Apache License, Version 2.0 which
* accompanies this distribution and is available at
* http://www.apache.org/licenses/LICENSE-2.0.txt
******************************************************************************/
package org.locationtech.spatial4j.shape.impl;
import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.shape.BaseShape;
import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Rectangle;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeCollection;
import org.locationtech.spatial4j.shape.SpatialRelation;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A BufferedLineString is a collection of {@link org.locationtech.spatial4j.shape.impl.BufferedLine} shapes,
* resulting in what some call a "Track" or "Polyline" (ESRI terminology).
* The buffer can be 0. Note that BufferedLine isn't yet aware of geodesics (e.g. the dateline).
*/
public class BufferedLineString extends BaseShape<SpatialContext> {
//TODO add some geospatial awareness like:
// segment that spans at the dateline (split it at DL?).
private final ShapeCollection<BufferedLine> segments;
private final double buf;
/**
* Needs at least 1 point, usually more than that. If just one then it's
* internally treated like 2 points.
*/
public BufferedLineString(List<Point> points, double buf, SpatialContext ctx) {
this(points, buf, false, ctx);
}
/**
* @param points ordered control points. If empty then this shape is empty.
* @param buf Buffer >= 0
* @param expandBufForLongitudeSkew See {@link BufferedLine
* #expandBufForLongitudeSkew(org.locationtech.spatial4j.shape.Point,
* org.locationtech.spatial4j.shape.Point, double)}.
* If true then the buffer for each segment
* is computed.
* @param ctx
*/
public BufferedLineString(List<Point> points, double buf, boolean expandBufForLongitudeSkew,
SpatialContext ctx) {
super(ctx);
this.buf = buf;
if (points.isEmpty()) {
this.segments = ctx.makeCollection(Collections.<BufferedLine>emptyList());
} else {
List<BufferedLine> segments = new ArrayList<BufferedLine>(points.size() - 1);
Point prevPoint = null;
for (Point point : points) {
if (prevPoint != null) {
double segBuf = buf;
if (expandBufForLongitudeSkew) {
//TODO this is faulty in that it over-buffers. See Issue#60.
segBuf = BufferedLine.expandBufForLongitudeSkew(prevPoint, point, buf);
}
segments.add(new BufferedLine(prevPoint, point, segBuf, ctx));
}
prevPoint = point;
}
if (segments.isEmpty()) {//TODO throw exception instead?
segments.add(new BufferedLine(prevPoint, prevPoint, buf, ctx));
}
this.segments = ctx.makeCollection(segments);
}
}
@Override
public boolean isEmpty() {
return segments.isEmpty();
}
@Override
public Shape getBuffered(double distance, SpatialContext ctx) {
return ctx.makeBufferedLineString(getPoints(), buf + distance);
}
public ShapeCollection<BufferedLine> getSegments() {
return segments;
}
public double getBuf() {
return buf;
}
@Override
public double getArea(SpatialContext ctx) {
return segments.getArea(ctx);
}
@Override
public SpatialRelation relate(Shape other) {
return segments.relate(other);
}
@Override
public boolean hasArea() {
return segments.hasArea();
}
@Override
public Point getCenter() {
return segments.getCenter();
}
@Override
public Rectangle getBoundingBox() {
return segments.getBoundingBox();
}
@Override
public String toString() {
StringBuilder str = new StringBuilder(100);
str.append("BufferedLineString(buf=").append(buf).append(" pts=");
boolean first = true;
for (Point point : getPoints()) {
if (first) {
first = false;
} else {
str.append(", ");
}
str.append(point.getX()).append(' ').append(point.getY());
}
str.append(')');
return str.toString();
}
public List<Point> getPoints() {
if (segments.isEmpty())
return Collections.emptyList();
final List<BufferedLine> lines = segments.getShapes();
return new AbstractList<Point>() {
@Override
public Point get(int index) {
if (index == 0)
return lines.get(0).getA();
return lines.get(index - 1).getB();
}
@Override
public int size() {
return lines.size() + 1;
}
};
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
BufferedLineString that = (BufferedLineString) o;
if (Double.compare(that.buf, buf) != 0) return false;
if (!segments.equals(that.segments)) return false;
return true;
}
@Override
public int hashCode() {
int result;
long temp;
result = segments.hashCode();
temp = buf != +0.0d ? Double.doubleToLongBits(buf) : 0L;
result = 31 * result + (int) (temp ^ (temp >>> 32));
return result;
}
}