/** * Copyright 2013 OpenSocial Foundation * Copyright 2013 International Business Machines Corporation * * Licensed 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. * * Utility library for working with Activity Streams Actions * Requires underscorejs. * * @author James M Snell (jasnell@us.ibm.com) */ package com.ibm.common.geojson; import static com.ibm.common.geojson.BoundingBox.calculateBoundingBoxPositions; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.concat; import static com.google.common.collect.Iterables.getFirst; import static com.google.common.collect.Iterables.size; import static java.lang.String.format; import java.io.ObjectStreamException; import java.util.Iterator; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.ibm.common.geojson.Geometry.CoordinateGeometry; /** * A GeoJSON LineString object * See http://geojson.org/geojson-spec.html#linestring. * * The LineString can optionally be a Linear Ring if there are at least four * positions and the first and last position are equivalent. * * <pre> * LineString regularLineString = * GeoMakers.linestring() * .add(1,2) * .add(2,3) * .get(); * // includes points (1,2) and (2,3) * * LineString linearRing = * GeoMakers.linestring() * .linearRing() * .add(1,2) * .add(2,3) * .add(3,4) * .get() * // includes points (1,2), (2,3), (3,4) and (1,2) * </pre> * * @author james * */ public final class LineString extends CoordinateGeometry<LineString,Position,Iterable<Position>> { public static final class Builder extends CoordinateGeometry.Builder<Position,Iterable<Position>,LineString, Builder> { private final ImmutableList.Builder<Position> positions = ImmutableList.builder(); private boolean ring; private final boolean nocheck; public Builder() { nocheck = false; type(Type.LINESTRING); } private Builder(boolean nocheck) { this.nocheck = nocheck; type(Type.LINESTRING); } /** * Specify that this LineString is a linearring * @return Builder */ public Builder linearRing() { return linearRing(true); } public Builder linearRing(boolean on) { this.ring = on; return this; } /** * Add one or more positions to this linestring * @param position Position * @param positions Position[] optional vararg * @return Builder */ public Builder add(Position position, Position... positions) { this.positions.add(position); if (positions != null) for (Position pos : positions) add(pos); return this; } /** * Add a single position to this linestring * @param x float * @param y float * @return Builder */ public Builder add(float x, float y) { return add(GeoObject.position(x, y)); } /** * Add a single position to this linestring * @param position Supplier<Position> * @return Builder */ public Builder add(Supplier<Position> position) { return add(position.get()); } /** * Add one or more positions to this linestring * @param positions Iterable<Position> * @return Builder */ public Builder add(Iterable<Position> positions) { this.positions.addAll(positions); return this; } /** * Add a single position to this linestring * @param x float * @param y float * @param z float * @return Builder */ public Builder add(float x, float y, float z) { return add(GeoObject.position(x,y,z)); } public LineString doGet() { return new LineString(this); } @Override protected Iterable<Position> coordinates() { return positions.build(); } } private final boolean ring; protected LineString( Builder builder) { super(builder); this.ring = builder.ring; int min = ring?3:2; checkArgument( builder.nocheck || size(coordinates()) >= min, format( "A LineString that %s a LinearRing MUST consist of at least %d positions", ring?"is":"is not", min)); } public boolean linearRing() { return ring; } @Override /** * Get this LineStrings positions * @return Iterable<Position> */ public Iterable<Position> coordinates() { Iterable<Position> pos = super.coordinates(); if (!ring) return pos; else { return size(pos) > 0 ? concat(pos,ImmutableList.of(getFirst(pos,null))) : ImmutableList.<Position>of(); } } @Override public Iterator<Position> iterator() { return coordinates().iterator(); } /** * Return a copy of this linestring with a calculated bounding box */ @Override public LineString makeWithBoundingBox() { return new LineString.Builder() .from(this) .add(this) .boundingBox(calculateBoundingBoxPositions(this)) .get(); } // Java Serialization support Object writeReplace() throws java.io.ObjectStreamException { return new SerializedForm(this); } private static class SerializedForm extends AbstractSerializedForm<LineString,Builder> { private static final long serialVersionUID = -2060301713159936281L; boolean ring; protected SerializedForm(LineString obj) { super(obj); this.ring = obj.ring; } Object readResolve() throws ObjectStreamException { return doReadResolve(); } @SuppressWarnings("unchecked") @Override protected boolean handle(Builder builder, String key, Object val) { if ("coordinates".equals(key)) { Iterable<Position> list = (Iterable<Position>) val; builder.positions.addAll(list); return true; } return false; } @Override protected LineString.Builder builder() { Builder builder = new Builder(true); builder.ring = ring; return builder; } } }