/** * 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 java.io.ObjectStreamException; import java.io.Serializable; import java.util.Iterator; import com.google.common.base.Objects; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.Iterables; /** * A GeoJSON Bounding Box (see http://geojson.org/geojson-spec.html#bounding-boxes) * @author james */ public final class BoundingBox implements Iterable<Float>, Serializable { public static final class Builder implements Supplier<BoundingBox> { private final ImmutableList.Builder<Float> bounds = ImmutableList.builder(); public Builder add(float value) { this.bounds.add(value); return this; } public Builder add(float... values) { if (values != null) for (float v : values) this.bounds.add(v); return this; } @Override public BoundingBox get() { return new BoundingBox(this); } } private final ImmutableList<Float> bounds; BoundingBox(Builder builder) { this.bounds = builder.bounds.build(); } @Override public Iterator<Float> iterator() { return bounds.iterator(); } public String toString() { return Objects.toStringHelper(BoundingBox.class) .addValue(bounds) .toString(); } private static BoundingBox buildBoundingBox( ImmutableSortedSet<Float> xs, ImmutableSortedSet<Float> ys, ImmutableSortedSet<Float> zs) { BoundingBox.Builder bbox = new BoundingBox.Builder() .add(xs.first()) .add(ys.first()); if (!zs.isEmpty()) bbox.add(zs.first()); bbox.add(xs.last()); bbox.add(ys.last()); if (!zs.isEmpty()) bbox.add(zs.last()); return bbox.get(); } protected static BoundingBox calculateBoundingBoxLineStrings(Iterable<LineString> lineStrings) { ImmutableSortedSet.Builder<Float> xset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> yset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> zset = ImmutableSortedSet.naturalOrder(); for (LineString ls : lineStrings) { for (Position p : ls) { xset.add(p.northing()); yset.add(p.easting()); if (p.hasAltitude()) zset.add(p.altitude()); } } return buildBoundingBox( xset.build(), yset.build(), zset.build()); } /** * Calculate the Bounding Box for a collection of Polygon objects * @param polygons Iterable<Polygon> * @return BoundingBox */ public static BoundingBox calculateBoundingBoxPolygons(Iterable<Polygon> polygons) { ImmutableSortedSet.Builder<Float> xset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> yset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> zset = ImmutableSortedSet.naturalOrder(); for (Polygon polygon : polygons) { for (LineString line : polygon) { for (Position pos : line) { xset.add(pos.northing()); yset.add(pos.easting()); if (pos.hasAltitude()) zset.add(pos.altitude()); } } } return buildBoundingBox( xset.build(), yset.build(), zset.build()); } protected static BoundingBox calculateBoundingBoxPositions(Iterable<Position> positions) { ImmutableSortedSet.Builder<Float> xset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> yset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> zset = ImmutableSortedSet.naturalOrder(); for (Position pos : positions) { xset.add(pos.northing()); yset.add(pos.easting()); if (pos.hasAltitude()) zset.add(pos.altitude()); } return buildBoundingBox( xset.build(), yset.build(), zset.build()); } protected static BoundingBox calculateBoundingBox(Position position) { BoundingBox.Builder bbox = new BoundingBox.Builder(); bbox.add(position.northing()); bbox.add(position.easting()); if (position.hasAltitude()) bbox.add(position.altitude()); return bbox.get(); } private static void addValues( ImmutableSortedSet.Builder<Float> xset, ImmutableSortedSet.Builder<Float> yset, ImmutableSortedSet.Builder<Float> zset, Iterable<Position> positions) { for (Position position : positions) { xset.add(position.northing()); yset.add(position.easting()); if (position.hasAltitude()) zset.add(position.altitude()); } } private static void addValuesLineString( ImmutableSortedSet.Builder<Float> xset, ImmutableSortedSet.Builder<Float> yset, ImmutableSortedSet.Builder<Float> zset, Iterable<LineString> lines) { for (LineString ls : lines) { addValues(xset,yset,zset,ls); } } private static void addValuesPolygon( ImmutableSortedSet.Builder<Float> xset, ImmutableSortedSet.Builder<Float> yset, ImmutableSortedSet.Builder<Float> zset, Iterable<Polygon> polygons) { for (Polygon poly : polygons) { addValuesLineString(xset,yset,zset,poly); } } protected static BoundingBox calculateBoundingBox(Geometry<?,?> geometry) { return calculateBoundingBoxGeometries( ImmutableList.<Geometry<?,?>>of(geometry)); } @SuppressWarnings("unchecked") protected static BoundingBox calculateBoundingBoxGeometries( Iterable<Geometry<?,?>> geometries) { ImmutableSortedSet.Builder<Float> xset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> yset = ImmutableSortedSet.naturalOrder(); ImmutableSortedSet.Builder<Float> zset = ImmutableSortedSet.naturalOrder(); for (Geometry<?,?> geo : geometries) { switch(geo.type()) { case POINT: Point point = (Point) geo; Position position = Iterables.getFirst(point.coordinates(),null); xset.add(position.northing()); yset.add(position.easting()); if (position.hasAltitude()) zset.add(position.altitude()); break; case LINESTRING: case MULTIPOINT: addValues(xset,yset,zset,(Iterable<Position>)geo); break; case MULTILINESTRING: case POLYGON: addValuesLineString(xset,yset,zset,(Iterable<LineString>)geo); break; case MULTIPOLYGON: addValuesPolygon(xset,yset,zset,(Iterable<Polygon>)geo); break; default: break; } } return buildBoundingBox( xset.build(), yset.build(), zset.build()); } protected static BoundingBox calculateBoundingBoxFeatures(Iterable<Feature> features) { ImmutableList.Builder<Geometry<?,?>> list = ImmutableList.builder(); for (Feature feature : features) { Geometry<?,?> geometry = feature.geometry(); if (geometry != null) list.add(geometry); } return calculateBoundingBoxGeometries(list.build()); } Object writeReplace() throws java.io.ObjectStreamException { return new SerializedForm(this); } private static class SerializedForm implements Serializable { private static final long serialVersionUID = -2060301713159936285L; private ImmutableList<Float> bounds; protected SerializedForm(BoundingBox obj) { this.bounds = obj.bounds; } Object readResolve() throws ObjectStreamException { BoundingBox.Builder builder = new BoundingBox.Builder(); builder.bounds.addAll(bounds); return builder.get(); } } }